#include "qlib/db/db.h"
namespace qlib {

DB::DB() {}
DB::~DB() {}

void compare_polygon(Polygon::s_ptr poly1) {}

ErrorCode DB::write_gds(const PropertyArray& properties, FILE* out) {
  uint64_t count = 0;

  for (auto& prop : properties) {
    if (!is_gds_property(prop))
      continue;

    const PropertyValue::s_ptr attribute = prop->values[0];
    const PropertyValue::s_ptr value = prop->values[1];

    uint64_t len = value->string.size();
    uint8_t* bytes = (uint8_t*)value->string.c_str();
    bool free_bytes = false;

    if (len % 2) {
      if (bytes[len - 1] == 0) {
        len--;
      } else {
        free_bytes = true;
        bytes = (uint8_t*)allocate(++len);
        memcpy(bytes, value->string.c_str(), len - 1);
        bytes[len - 1] = 0;
      }
    }

    uint16_t buffer_prop[] = {6, 0x2B02, (uint16_t)attribute->unsigned_integer, (uint16_t)(4 + len), 0x2C06};
    count += len;
    big_endian_swap16(buffer_prop, COUNT(buffer_prop));
    fwrite(buffer_prop, sizeof(uint16_t), COUNT(buffer_prop), out);
    fwrite(bytes, 1, len, out);

    if (free_bytes)
      free_allocation(bytes);
  }

  if (count > 128) {
    if (error_logger)
      fputs(
          "[GDSTK] Properties with count larger than 128 bytes are not officially supported by the GDSII specification.  This file might not be "
          "compatible with all readers.\n",
          error_logger);
    return ErrorCode::UnofficialSpecification;
  }
  return ErrorCode::NoError;
}

// ErrorCode DB::circle_write_gds(const Circle::s_ptr circle, FILE* out, double scaling) {
//   Polygon::s_ptr poly = circle->to_polygon();
//   write_gds(poly, out, scaling);
// }
// ErrorCode DB::rectangle_write_gds(const Rectangle::s_ptr rectange, FILE* out, double scaling) {
//   Polygon::s_ptr poly = rectange->to_polygon();
//   write_gds(poly, out, scaling);
// }

ErrorCode DB::write_gds(const Polygon::s_ptr poly, FILE* out, double scaling) {
  ErrorCode error_code = ErrorCode::NoError;

  if (poly->points.size() < 3)
    return error_code;

  uint16_t buffer_start[] = {4, 0x0800, 6, 0x0D02, (uint16_t)poly->layer, 6, 0x0E02, (uint16_t)poly->datatype};
  uint16_t buffer_end[] = {4, 0x1100};
  big_endian_swap16(buffer_start, COUNT(buffer_start));
  big_endian_swap16(buffer_end, COUNT(buffer_end));

  uint64_t total = poly->points.size() + 1;
  if (total > 8190) {
    if (error_logger)
      fputs(
          "[GDSTK] Polygons with more than 8190 are not supported by the official GDSII specification. This GDSII file might not be compatible "
          "with "
          "all readers.\n",
          error_logger);
    error_code = ErrorCode::UnofficialSpecification;
  }
  Array<int32_t> coords = {};
  coords.resize(2 * total);

  Vec2d zero = {0, 0};
  Array<Vec2d> offsets = {};
  if (poly->repetition.type != RepetitionType::None) {
    poly->repetition.get_offsets(offsets);
  } else {
    offsets.push_back(zero);
  }

  double* offset_p = (double*)offsets.data();
  for (uint64_t offset_count = offsets.size(); offset_count > 0; offset_count--) {
    fwrite(buffer_start, sizeof(uint16_t), COUNT(buffer_start), out);

    double offset_x = *offset_p++;
    double offset_y = *offset_p++;
    int32_t* c = coords.data();
    Vec2d* p = poly->points.data();
    for (uint64_t j = poly->points.size(); j > 0; j--) {
      *c++ = (int32_t)lround((offset_x + p->x) * scaling);
      *c++ = (int32_t)lround((offset_y + p->y) * scaling);
      p++;
    }
    *c++ = coords[0];
    *c++ = coords[1];
    big_endian_swap32((uint32_t*)coords.data(), coords.size());

    uint64_t i0 = 0;
    while (i0 < total) {
      uint64_t i1 = total < i0 + 8190 ? total : i0 + 8190;
      uint16_t buffer_pts[] = {(uint16_t)(4 + 8 * (i1 - i0)), 0x1003};
      big_endian_swap16(buffer_pts, COUNT(buffer_pts));
      fwrite(buffer_pts, sizeof(uint16_t), COUNT(buffer_pts), out);
      fwrite(coords.data() + 2 * i0, sizeof(int32_t), 2 * (i1 - i0), out);
      i0 = i1;
    }

    ErrorCode err = write_gds(poly->properties, out);
    if (err != ErrorCode::NoError)
      error_code = err;

    fwrite(buffer_end, sizeof(uint16_t), COUNT(buffer_end), out);
  }

  if (poly->repetition.type != RepetitionType::None)
    offsets.clear();
  coords.clear();
  return error_code;
}

ErrorCode DB::write_gds(const KeyPoint::s_ptr label, FILE* out, double scaling) {
  ErrorCode error_code = ErrorCode::NoError;

  uint16_t buffer_start[] = {4, 0x0C00, 6, 0x0D02, (uint16_t)label->layer, 6, 0x1602, (uint16_t)label->datatype, 6, 0x1701, (uint16_t)label->anchor};
  big_endian_swap16(buffer_start, COUNT(buffer_start));

  uint16_t buffer_end[] = {4, 0x1100};
  big_endian_swap16(buffer_end, COUNT(buffer_end));

  uint16_t buffer_xy[] = {12, 0x1003};
  big_endian_swap16(buffer_xy, COUNT(buffer_xy));

  uint64_t len = label->text.size();

  if (len % 2)
    len++;
  uint16_t buffer_text[] = {(uint16_t)(4 + len), 0x1906};
  big_endian_swap16(buffer_text, COUNT(buffer_text));

  bool transform_ = label->rotation != 0 || label->magnification != 1 || label->x_reflection;
  uint16_t buffer_flags[] = {6, 0x1A01, 0};
  uint16_t buffer_mag[] = {12, 0x1B05};
  uint16_t buffer_rot[] = {12, 0x1C05};
  uint64_t mag_real, rot_real;
  if (transform_) {
    if (label->x_reflection) {
      buffer_flags[2] |= 0x8000;
    }
    if (label->magnification != 1) {
      // if ("absolute magnification") buffer_flags[2] |= 0x0004; // UNSUPPORTED
      big_endian_swap16(buffer_mag, COUNT(buffer_mag));
      mag_real = gdsii_real_from_double(label->magnification);
      big_endian_swap64(&mag_real, 1);
    }
    if (label->rotation != 0) {
      // if ("absolute rotation") buffer_flags[2] |= 0x0002; // UNSUPPORTED
      big_endian_swap16(buffer_rot, COUNT(buffer_rot));
      rot_real = gdsii_real_from_double(label->rotation * (180.0 / M_PI));
      big_endian_swap64(&rot_real, 1);
    }
    big_endian_swap16(buffer_flags, COUNT(buffer_flags));
  }

  Vec2d zero = {0, 0};
  Array<Vec2d> offsets = {};
  if (label->repetition.type != RepetitionType::None) {
    label->repetition.get_offsets(offsets);
  } else {
    offsets.push_back(zero);
  }

  Vec2d* offset_p = offsets.data();
  for (uint64_t offset_count = offsets.size(); offset_count > 0; offset_count--, offset_p++) {
    fwrite(buffer_start, sizeof(uint16_t), COUNT(buffer_start), out);

    if (transform_) {
      fwrite(buffer_flags, sizeof(uint16_t), COUNT(buffer_flags), out);
      if (label->magnification != 1) {
        fwrite(buffer_mag, sizeof(uint16_t), COUNT(buffer_mag), out);
        fwrite(&mag_real, sizeof(uint64_t), 1, out);
      }
      if (label->rotation != 0) {
        fwrite(buffer_rot, sizeof(uint16_t), COUNT(buffer_rot), out);
        fwrite(&rot_real, sizeof(uint64_t), 1, out);
      }
    }

    int32_t buffer_pos[] = {(int32_t)(lround((label->origin.x + offset_p->x) * scaling)),
                            (int32_t)(lround((label->origin.y + offset_p->y) * scaling))};
    big_endian_swap32((uint32_t*)buffer_pos, COUNT(buffer_pos));

    fwrite(buffer_xy, sizeof(uint16_t), COUNT(buffer_xy), out);
    fwrite(buffer_pos, sizeof(uint32_t), COUNT(buffer_pos), out);

    fwrite(buffer_text, sizeof(uint16_t), COUNT(buffer_text), out);
    fwrite(label->text.c_str(), 1, len, out);

    ErrorCode err = write_gds(label->properties, out);
    if (err != ErrorCode::NoError)
      error_code = err;
    fwrite(buffer_end, sizeof(uint16_t), COUNT(buffer_end), out);
  }
  if (label->repetition.type != RepetitionType::None)
    offsets.clear();
  return error_code;
}

ErrorCode DB::write_gds(const Pin::s_ptr label, FILE* out, double scaling) {
  ErrorCode error_code = ErrorCode::NoError;

  uint16_t buffer_start[] = {4, 0x0C00, 6, 0x0D02, (uint16_t)label->layer, 6, 0x1602, (uint16_t)label->datatype, 6, 0x1701, (uint16_t)label->anchor};
  big_endian_swap16(buffer_start, COUNT(buffer_start));

  uint16_t buffer_end[] = {4, 0x1100};
  big_endian_swap16(buffer_end, COUNT(buffer_end));

  uint16_t buffer_xy[] = {12, 0x1003};
  big_endian_swap16(buffer_xy, COUNT(buffer_xy));

  uint64_t len = label->text.size();

  if (len % 2)
    len++;
  uint16_t buffer_text[] = {(uint16_t)(4 + len), 0x1906};
  big_endian_swap16(buffer_text, COUNT(buffer_text));

  bool transform_ = label->rotation != 0 || label->magnification != 1 || label->x_reflection;
  uint16_t buffer_flags[] = {6, 0x1A01, 0};
  uint16_t buffer_mag[] = {12, 0x1B05};
  uint16_t buffer_rot[] = {12, 0x1C05};
  uint64_t mag_real, rot_real;
  if (transform_) {
    if (label->x_reflection) {
      buffer_flags[2] |= 0x8000;
    }
    if (label->magnification != 1) {
      // if ("absolute magnification") buffer_flags[2] |= 0x0004; // UNSUPPORTED
      big_endian_swap16(buffer_mag, COUNT(buffer_mag));
      mag_real = gdsii_real_from_double(label->magnification);
      big_endian_swap64(&mag_real, 1);
    }
    if (label->rotation != 0) {
      // if ("absolute rotation") buffer_flags[2] |= 0x0002; // UNSUPPORTED
      big_endian_swap16(buffer_rot, COUNT(buffer_rot));
      rot_real = gdsii_real_from_double(label->rotation * (180.0 / M_PI));
      big_endian_swap64(&rot_real, 1);
    }
    big_endian_swap16(buffer_flags, COUNT(buffer_flags));
  }

  Vec2d zero = {0, 0};
  Array<Vec2d> offsets = {};
  if (label->repetition.type != RepetitionType::None) {
    label->repetition.get_offsets(offsets);
  } else {
    offsets.push_back(zero);
  }

  Vec2d* offset_p = offsets.data();
  for (uint64_t offset_count = offsets.size(); offset_count > 0; offset_count--, offset_p++) {
    fwrite(buffer_start, sizeof(uint16_t), COUNT(buffer_start), out);

    if (transform_) {
      fwrite(buffer_flags, sizeof(uint16_t), COUNT(buffer_flags), out);
      if (label->magnification != 1) {
        fwrite(buffer_mag, sizeof(uint16_t), COUNT(buffer_mag), out);
        fwrite(&mag_real, sizeof(uint64_t), 1, out);
      }
      if (label->rotation != 0) {
        fwrite(buffer_rot, sizeof(uint16_t), COUNT(buffer_rot), out);
        fwrite(&rot_real, sizeof(uint64_t), 1, out);
      }
    }

    int32_t buffer_pos[] = {(int32_t)(lround((label->origin.x + offset_p->x) * scaling)),
                            (int32_t)(lround((label->origin.y + offset_p->y) * scaling))};
    big_endian_swap32((uint32_t*)buffer_pos, COUNT(buffer_pos));

    fwrite(buffer_xy, sizeof(uint16_t), COUNT(buffer_xy), out);
    fwrite(buffer_pos, sizeof(uint32_t), COUNT(buffer_pos), out);

    fwrite(buffer_text, sizeof(uint16_t), COUNT(buffer_text), out);
    fwrite(label->text.c_str(), 1, len, out);

    ErrorCode err = write_gds(label->properties, out);
    if (err != ErrorCode::NoError)
      error_code = err;
    fwrite(buffer_end, sizeof(uint16_t), COUNT(buffer_end), out);
  }
  if (label->repetition.type != RepetitionType::None)
    offsets.clear();
  return error_code;
}

ErrorCode DB::write_gds(const Label::s_ptr label, FILE* out, double scaling) {
  ErrorCode error_code = ErrorCode::NoError;

  label->savePropertiesGDS();

  uint16_t buffer_start[] = {4, 0x0C00, 6, 0x0D02, (uint16_t)label->layer, 6, 0x1602, (uint16_t)label->datatype, 6, 0x1701, (uint16_t)label->anchor};
  big_endian_swap16(buffer_start, COUNT(buffer_start));

  uint16_t buffer_end[] = {4, 0x1100};
  big_endian_swap16(buffer_end, COUNT(buffer_end));

  uint16_t buffer_xy[] = {12, 0x1003};
  big_endian_swap16(buffer_xy, COUNT(buffer_xy));

  label->saveToProper();

  uint64_t len = label->text.size();

  if (len % 2)
    len++;
  uint16_t buffer_text[] = {(uint16_t)(4 + len), 0x1906};
  big_endian_swap16(buffer_text, COUNT(buffer_text));

  bool transform_ = label->rotation != 0 || label->magnification != 1 || label->x_reflection;
  uint16_t buffer_flags[] = {6, 0x1A01, 0};
  uint16_t buffer_mag[] = {12, 0x1B05};
  uint16_t buffer_rot[] = {12, 0x1C05};
  uint64_t mag_real, rot_real;
  if (transform_) {
    if (label->x_reflection) {
      buffer_flags[2] |= 0x8000;
    }
    if (label->magnification != 1) {
      // if ("absolute magnification") buffer_flags[2] |= 0x0004; // UNSUPPORTED
      big_endian_swap16(buffer_mag, COUNT(buffer_mag));
      mag_real = gdsii_real_from_double(label->magnification);
      big_endian_swap64(&mag_real, 1);
    }
    if (label->rotation != 0) {
      // if ("absolute rotation") buffer_flags[2] |= 0x0002; // UNSUPPORTED
      big_endian_swap16(buffer_rot, COUNT(buffer_rot));
      rot_real = gdsii_real_from_double(label->rotation * (180.0 / M_PI));
      big_endian_swap64(&rot_real, 1);
    }
    big_endian_swap16(buffer_flags, COUNT(buffer_flags));
  }

  Vec2d zero = {0, 0};
  Array<Vec2d> offsets = {};
  if (label->repetition.type != RepetitionType::None) {
    label->repetition.get_offsets(offsets);
  } else {
    offsets.push_back(zero);
  }

  Vec2d* offset_p = offsets.data();
  for (uint64_t offset_count = offsets.size(); offset_count > 0; offset_count--, offset_p++) {
    fwrite(buffer_start, sizeof(uint16_t), COUNT(buffer_start), out);

    if (transform_) {
      fwrite(buffer_flags, sizeof(uint16_t), COUNT(buffer_flags), out);
      if (label->magnification != 1) {
        fwrite(buffer_mag, sizeof(uint16_t), COUNT(buffer_mag), out);
        fwrite(&mag_real, sizeof(uint64_t), 1, out);
      }
      if (label->rotation != 0) {
        fwrite(buffer_rot, sizeof(uint16_t), COUNT(buffer_rot), out);
        fwrite(&rot_real, sizeof(uint64_t), 1, out);
      }
    }

    int32_t buffer_pos[] = {(int32_t)(lround((label->origin.x + offset_p->x) * scaling)),
                            (int32_t)(lround((label->origin.y + offset_p->y) * scaling))};
    big_endian_swap32((uint32_t*)buffer_pos, COUNT(buffer_pos));

    fwrite(buffer_xy, sizeof(uint16_t), COUNT(buffer_xy), out);
    fwrite(buffer_pos, sizeof(uint32_t), COUNT(buffer_pos), out);

    fwrite(buffer_text, sizeof(uint16_t), COUNT(buffer_text), out);
    fwrite(label->text.c_str(), 1, len, out);

    ErrorCode err = write_gds(label->properties, out);
    if (err != ErrorCode::NoError)
      error_code = err;
    fwrite(buffer_end, sizeof(uint16_t), COUNT(buffer_end), out);
  }
  if (label->repetition.type != RepetitionType::None)
    offsets.clear();
  return error_code;
}

ErrorCode DB::write_gds(const Text::s_ptr label, FILE* out, double scaling) {
  ErrorCode error_code = ErrorCode::NoError;
  uint16_t buffer_start[] = {4, 0x0C00, 6, 0x0D02, (uint16_t)label->layer, 6, 0x1602, (uint16_t)label->datatype, 6, 0x1701, (uint16_t)label->anchor};
  big_endian_swap16(buffer_start, COUNT(buffer_start));

  uint16_t buffer_end[] = {4, 0x1100};
  big_endian_swap16(buffer_end, COUNT(buffer_end));

  uint16_t buffer_xy[] = {12, 0x1003};
  big_endian_swap16(buffer_xy, COUNT(buffer_xy));

  uint64_t len = label->text.size();

  if (len % 2)
    len++;
  uint16_t buffer_text[] = {(uint16_t)(4 + len), 0x1906};
  big_endian_swap16(buffer_text, COUNT(buffer_text));

  bool transform_ = label->rotation != 0 || label->magnification != 1 || label->x_reflection;
  uint16_t buffer_flags[] = {6, 0x1A01, 0};
  uint16_t buffer_mag[] = {12, 0x1B05};
  uint16_t buffer_rot[] = {12, 0x1C05};
  uint64_t mag_real, rot_real;
  if (transform_) {
    if (label->x_reflection) {
      buffer_flags[2] |= 0x8000;
    }
    if (label->magnification != 1) {
      // if ("absolute magnification") buffer_flags[2] |= 0x0004; // UNSUPPORTED
      big_endian_swap16(buffer_mag, COUNT(buffer_mag));
      mag_real = gdsii_real_from_double(label->magnification);
      big_endian_swap64(&mag_real, 1);
    }
    if (label->rotation != 0) {
      // if ("absolute rotation") buffer_flags[2] |= 0x0002; // UNSUPPORTED
      big_endian_swap16(buffer_rot, COUNT(buffer_rot));
      rot_real = gdsii_real_from_double(label->rotation * (180.0 / M_PI));
      big_endian_swap64(&rot_real, 1);
    }
    big_endian_swap16(buffer_flags, COUNT(buffer_flags));
  }

  Vec2d zero = {0, 0};
  Array<Vec2d> offsets = {};
  if (label->repetition.type != RepetitionType::None) {
    label->repetition.get_offsets(offsets);
  } else {
    offsets.push_back(zero);
  }

  Vec2d* offset_p = offsets.data();
  for (uint64_t offset_count = offsets.size(); offset_count > 0; offset_count--, offset_p++) {
    fwrite(buffer_start, sizeof(uint16_t), COUNT(buffer_start), out);

    if (transform_) {
      fwrite(buffer_flags, sizeof(uint16_t), COUNT(buffer_flags), out);
      if (label->magnification != 1) {
        fwrite(buffer_mag, sizeof(uint16_t), COUNT(buffer_mag), out);
        fwrite(&mag_real, sizeof(uint64_t), 1, out);
      }
      if (label->rotation != 0) {
        fwrite(buffer_rot, sizeof(uint16_t), COUNT(buffer_rot), out);
        fwrite(&rot_real, sizeof(uint64_t), 1, out);
      }
    }

    int32_t buffer_pos[] = {(int32_t)(lround((label->origin.x + offset_p->x) * scaling)),
                            (int32_t)(lround((label->origin.y + offset_p->y) * scaling))};
    big_endian_swap32((uint32_t*)buffer_pos, COUNT(buffer_pos));

    fwrite(buffer_xy, sizeof(uint16_t), COUNT(buffer_xy), out);
    fwrite(buffer_pos, sizeof(uint32_t), COUNT(buffer_pos), out);

    fwrite(buffer_text, sizeof(uint16_t), COUNT(buffer_text), out);
    fwrite(label->text.c_str(), 1, len, out);

    ErrorCode err = write_gds(label->properties, out);
    if (err != ErrorCode::NoError)
      error_code = err;
    fwrite(buffer_end, sizeof(uint16_t), COUNT(buffer_end), out);
  }
  if (label->repetition.type != RepetitionType::None)
    offsets.clear();
  return error_code;
}

ErrorCode DB::write_gds(const Path::s_ptr line, FILE* out, double scaling) {
  ErrorCode error_code = ErrorCode::NoError;

  line->remove_overlapping_points();

  if (line->spine.points.size() < 2)
    return error_code;

  uint16_t buffer_end[] = {4, 0x1100};
  big_endian_swap16(buffer_end, COUNT(buffer_end));

  Vec2d zero = {0, 0};
  Array<Vec2d> offsets = {};
  if (line->repetition.type != RepetitionType::None) {
    line->repetition.get_offsets(offsets);
  } else {
    offsets.push_back(zero);
  }

  Array<int32_t> coords = {};
  Array<Vec2d> point_array = {};
  point_array.reserve(line->spine.points.size());

  LineElement* el = line->elements.data();
  for (uint64_t ne = 0; ne < line->elements.size(); ne++, el++) {
    uint16_t end_type;
    switch (el->end_type) {
      case EndType::HalfWidth:
        end_type = 2;
        break;
      case EndType::Extended:
        end_type = 4;
        break;
      case EndType::Round:
        end_type = 1;
        break;
      case EndType::Smooth:
        end_type = 1;
        break;
      default:
        end_type = 0;
    }

    uint16_t path_type = (!line->m_raith_data.base_cell_name.empty()) ? 0x5A00 : 0x0900;
    uint16_t buffer_start[] = {4, path_type, 6, 0x0D02, (uint16_t)line->layer, 6, 0x0E02, (uint16_t)line->datatype, 6, 0x2102, end_type, 8, 0x0F03};

    PXXData pxxdata = line->m_raith_data.to_pxxdata();
    pxxdata.little_endian_swap();

    uint64_t len = line->m_raith_data.base_cell_name.size();

    if (len % 2)
      len++;
    uint16_t sname_start[] = {(uint16_t)(4 + len), 0x1206};
    big_endian_swap16(sname_start, COUNT(sname_start));

    int32_t width = (line->scale_width ? 1 : -1) * (int32_t)lround(2 * el->half_width_and_offset[0].u * scaling);
    big_endian_swap16(buffer_start, COUNT(buffer_start));
    big_endian_swap32((uint32_t*)&width, 1);

    uint16_t buffer_ext1[] = {8, 0x3003};
    uint16_t buffer_ext2[] = {8, 0x3103};
    int32_t ext_size[] = {0, 0};
    if (end_type == 4) {
      ext_size[0] = (int32_t)lround(el->end_extensions.u * scaling);
      ext_size[1] = (int32_t)lround(el->end_extensions.v * scaling);
      big_endian_swap16(buffer_ext1, COUNT(buffer_ext1));
      big_endian_swap16(buffer_ext2, COUNT(buffer_ext2));
      big_endian_swap32((uint32_t*)ext_size, COUNT(ext_size));
    }

    ErrorCode err = line->element_center(el, point_array);
    if (err != ErrorCode::NoError)
      error_code = err;
    coords.resize(point_array.size() * 2);

    double* offset_p = (double*)offsets.data();
    for (uint64_t offset_count = offsets.size(); offset_count > 0; offset_count--) {
      fwrite(buffer_start, sizeof(uint16_t), COUNT(buffer_start), out);
      fwrite(&width, sizeof(int32_t), 1, out);
      if (!line->m_raith_data.base_cell_name.empty()) {
        fwrite(sname_start, sizeof(uint16_t), COUNT(sname_start), out);
        fwrite(line->m_raith_data.base_cell_name.c_str(), 1, len, out);
        uint16_t buffer_pxx[] = {(uint16_t)(4 + sizeof(PXXData)), 0x6206};
        big_endian_swap16(buffer_pxx, COUNT(buffer_pxx));
        fwrite(buffer_pxx, sizeof(uint16_t), COUNT(buffer_pxx), out);
        fwrite(&pxxdata, 1, sizeof(PXXData), out);
      }

      if (end_type == 4) {
        fwrite(buffer_ext1, sizeof(uint16_t), COUNT(buffer_ext1), out);
        fwrite(ext_size, sizeof(int32_t), 1, out);
        fwrite(buffer_ext2, sizeof(uint16_t), COUNT(buffer_ext2), out);
        fwrite(ext_size + 1, sizeof(int32_t), 1, out);
      }

      int32_t* c = coords.data();
      double* p = (double*)point_array.data();
      double offset_x = *offset_p++;
      double offset_y = *offset_p++;
      for (uint64_t i = point_array.size(); i > 0; i--) {
        *c++ = (int32_t)lround((*p++ + offset_x) * scaling);
        *c++ = (int32_t)lround((*p++ + offset_y) * scaling);
      }
      big_endian_swap32((uint32_t*)coords.data(), coords.size());

      uint64_t total = point_array.size();
      uint64_t i0 = 0;
      while (i0 < total) {
        uint64_t i1 = total < i0 + 8190 ? total : i0 + 8190;
        uint16_t buffer_pts[] = {(uint16_t)(4 + 8 * (i1 - i0)), 0x1003};
        big_endian_swap16(buffer_pts, COUNT(buffer_pts));
        fwrite(buffer_pts, sizeof(uint16_t), COUNT(buffer_pts), out);
        fwrite(coords.data() + 2 * i0, sizeof(int32_t), 2 * (i1 - i0), out);
        i0 = i1;
      }

      err = write_gds(line->properties, out);
      if (err != ErrorCode::NoError)
        error_code = err;

      fwrite(buffer_end, sizeof(uint16_t), COUNT(buffer_end), out);
    }

    point_array.clear();
    coords.clear();
  }

  coords.clear();
  point_array.clear();
  if (line->repetition.type != RepetitionType::None)
    offsets.clear();
  return error_code;
}

#define GDSTK_REFERENCE_REPETITION_TOLERANCE 1e-12
ErrorCode DB::write_gds(const Reference::s_ptr refer, FILE* out, double scaling) {
  ErrorCode error_code = ErrorCode::NoError;
  bool array = false;
  double x2, y2, x3, y3;
  Vec2d zero = {0, 0};
  Array<Vec2d> offsets = {};
  offsets.push_back(zero);

  uint16_t buffer_array[] = {8, 0x1302, 0, 0, 28, 0x1003};
  int32_t buffer_coord[6];
  uint16_t buffer_single[] = {12, 0x1003};
  big_endian_swap16(buffer_single, COUNT(buffer_single));

  uint64_t columns = refer->repetition.columns;
  uint64_t rows = refer->repetition.rows;
  if (refer->repetition.type != RepetitionType::None) {
    int64_t m = 0;
    if (refer->repetition.type == RepetitionType::Regular ||
        (refer->repetition.type == RepetitionType::Rectangular && is_multiple_of_pi_over_2(refer->rotation, m))) {
      Vec2d v1, v2;
      if (refer->repetition.type == RepetitionType::Rectangular) {
        v1.x = refer->repetition.spacing.x;
        v1.y = 0;
        v2.x = 0;
        v2.y = refer->repetition.spacing.y;
      } else {
        v1 = refer->repetition.v1;
        v2 = refer->repetition.v2;
      }
      Vec2d u1 = v1;
      Vec2d u2 = v2;
      double len1 = u1.normalize();
      double len2 = u2.normalize();
      double sa = sin(refer->rotation);
      double ca = cos(refer->rotation);
      double p1 = u1.inner(Vec2d{ca, sa});
      double p2 = u2.inner(Vec2d{-sa, ca});
      double p3 = u1.inner(Vec2d{-sa, ca});
      double p4 = u2.inner(Vec2d{ca, sa});
      if ((len1 == 0 || fabs(fabs(p1) - 1.0) < GDSTK_REFERENCE_REPETITION_TOLERANCE) &&
          (len2 == 0 || fabs(fabs(p2) - 1.0) < GDSTK_REFERENCE_REPETITION_TOLERANCE)) {
        array = true;
        x2 = refer->origin.x + columns * v1.x;
        y2 = refer->origin.y + columns * v1.y;
        x3 = refer->origin.x + rows * v2.x;
        y3 = refer->origin.y + rows * v2.y;
      } else if ((len1 == 0 || fabs(fabs(p3) - 1.0) < GDSTK_REFERENCE_REPETITION_TOLERANCE) &&
                 (len2 == 0 || fabs(fabs(p4) - 1.0) < GDSTK_REFERENCE_REPETITION_TOLERANCE)) {
        array = true;
        columns = refer->repetition.rows;
        rows = refer->repetition.columns;
        x2 = refer->origin.x + columns * v2.x;
        y2 = refer->origin.y + columns * v2.y;
        x3 = refer->origin.x + rows * v1.x;
        y3 = refer->origin.y + rows * v1.y;
      }
    }

    if (array) {
      if (refer->repetition.columns > UINT16_MAX || refer->repetition.rows > UINT16_MAX) {
        if (error_logger)
          fputs("[GDSTK] Repetition with more than 65535 columns or rows cannot be saved to a GDSII file.\n", error_logger);
        error_code = ErrorCode::InvalidRepetition;
        buffer_array[2] = UINT16_MAX;
        buffer_array[3] = UINT16_MAX;
      } else {
        buffer_array[2] = (uint16_t)columns;
        buffer_array[3] = (uint16_t)rows;
      }
      big_endian_swap16(buffer_array, COUNT(buffer_array));
      buffer_coord[0] = (int32_t)(lround(refer->origin.x * scaling));
      buffer_coord[1] = (int32_t)(lround(refer->origin.y * scaling));
      buffer_coord[2] = (int32_t)(lround(x2 * scaling));
      buffer_coord[3] = (int32_t)(lround(y2 * scaling));
      buffer_coord[4] = (int32_t)(lround(x3 * scaling));
      buffer_coord[5] = (int32_t)(lround(y3 * scaling));
      big_endian_swap32((uint32_t*)buffer_coord, COUNT(buffer_coord));
    } else {
      offsets.clear();
      refer->repetition.get_offsets(offsets);
    }
  }

  const char* ref_name = (refer->type == ReferenceType::Cell ? refer->cell->name.c_str() : refer->name.c_str());
  uint64_t len = strlen(ref_name);

  if (len % 2)
    len++;
  uint16_t buffer_start[] = {4, 0x0A00, (uint16_t)(4 + len), 0x1206};
  if (array)
    buffer_start[1] = 0x0B00;
  big_endian_swap16(buffer_start, COUNT(buffer_start));

  uint16_t buffer_end[] = {4, 0x1100};
  big_endian_swap16(buffer_end, COUNT(buffer_end));

  bool transform_ = refer->rotation != 0 || refer->magnification != 1 || refer->x_reflection;
  uint16_t buffer_flags[] = {6, 0x1A01, 0};
  uint16_t buffer_mag[] = {12, 0x1B05};
  uint16_t buffer_rot[] = {12, 0x1C05};
  uint64_t mag_real, rot_real;
  if (transform_) {
    if (refer->x_reflection) {
      buffer_flags[2] |= 0x8000;
    }
    if (refer->magnification != 1) {
      // if("absolute magnification") buffer_flags[2] |= 0x0004; UNSUPPORTED
      big_endian_swap16(buffer_mag, COUNT(buffer_mag));
      mag_real = gdsii_real_from_double(refer->magnification);
      big_endian_swap64(&mag_real, 1);
    }
    if (refer->rotation != 0) {
      // if("absolute rotation") buffer_flags[2] |= 0x0002; UNSUPPORTED
      big_endian_swap16(buffer_rot, COUNT(buffer_rot));
      rot_real = gdsii_real_from_double(refer->rotation * (180.0 / M_PI));
      big_endian_swap64(&rot_real, 1);
    }
    big_endian_swap16(buffer_flags, COUNT(buffer_flags));
  }

  Vec2d* offset_p = offsets.data();
  for (uint64_t offset_count = offsets.size(); offset_count > 0; offset_count--, offset_p++) {
    fwrite(buffer_start, sizeof(uint16_t), COUNT(buffer_start), out);
    fwrite(ref_name, 1, len, out);

    if (transform_) {
      fwrite(buffer_flags, sizeof(uint16_t), COUNT(buffer_flags), out);
      if (refer->magnification != 1) {
        fwrite(buffer_mag, sizeof(uint16_t), COUNT(buffer_mag), out);
        fwrite(&mag_real, sizeof(uint64_t), 1, out);
      }
      if (refer->rotation != 0) {
        fwrite(buffer_rot, sizeof(uint16_t), COUNT(buffer_rot), out);
        fwrite(&rot_real, sizeof(uint64_t), 1, out);
      }
    }

    if (array) {
      fwrite(buffer_array, sizeof(uint16_t), COUNT(buffer_array), out);
      fwrite(buffer_coord, sizeof(int32_t), COUNT(buffer_coord), out);
    } else {
      fwrite(buffer_single, sizeof(uint16_t), COUNT(buffer_single), out);
      int32_t buffer_single_coord[] = {(int32_t)(lround((refer->origin.x + offset_p->x) * scaling)),
                                       (int32_t)(lround((refer->origin.y + offset_p->y) * scaling))};
      big_endian_swap32((uint32_t*)buffer_single_coord, COUNT(buffer_single_coord));
      fwrite(buffer_single_coord, sizeof(int32_t), COUNT(buffer_single_coord), out);
    }

    ErrorCode err = write_gds(refer->properties, out);
    if (err != ErrorCode::NoError)
      error_code = err;
    fwrite(buffer_end, sizeof(uint16_t), COUNT(buffer_end), out);
  }

  if (refer->repetition.type != RepetitionType::None && !array)
    offsets.clear();
  return error_code;
}

ErrorCode DB::write_gds(const Cell::s_ptr cell, FILE* out, double scaling, uint64_t max_points, double precision, const tm* timestamp) {
  ErrorCode error_code = ErrorCode::NoError;
  uint64_t len = cell->name.size();

  if (len % 2)
    len++;
  uint16_t buffer_start[] = {28,
                             0x0502,
                             (uint16_t)(timestamp->tm_year + 1900),
                             (uint16_t)(timestamp->tm_mon + 1),
                             (uint16_t)timestamp->tm_mday,
                             (uint16_t)timestamp->tm_hour,
                             (uint16_t)timestamp->tm_min,
                             (uint16_t)timestamp->tm_sec,
                             (uint16_t)(timestamp->tm_year + 1900),
                             (uint16_t)(timestamp->tm_mon + 1),
                             (uint16_t)timestamp->tm_mday,
                             (uint16_t)timestamp->tm_hour,
                             (uint16_t)timestamp->tm_min,
                             (uint16_t)timestamp->tm_sec,
                             (uint16_t)(4 + len),
                             0x0606};
  big_endian_swap16(buffer_start, COUNT(buffer_start));
  fwrite(buffer_start, sizeof(uint16_t), COUNT(buffer_start), out);
  fwrite(cell->name.c_str(), 1, len, out);

  {
    Array<Polygon::s_ptr> fractured_array = {};

    auto view = cell->reg.view<Polygon::s_ptr>();
    view.each([&max_points, &precision, &fractured_array, &out, &scaling, &error_code](Polygon::s_ptr polygon) {
      if (max_points > 4 && polygon->points.size() > max_points) {
        polygon->fracture(max_points, precision, fractured_array);
        Polygon::s_ptr* a_item = fractured_array.data();
        for (uint64_t j = 0; j < fractured_array.size(); j++, a_item++) {
          Polygon::s_ptr p = *a_item;
          ErrorCode err = write_gds(p, out, scaling);
          if (err != ErrorCode::NoError)
            error_code = err;
          p = nullptr;
        }
        fractured_array.clear();
      } else {
        ErrorCode err = write_gds(polygon, out, scaling);
        if (err != ErrorCode::NoError)
          error_code = err;
      }
    });
  }

  {
    Array<Polygon::s_ptr> fractured_array = {};

    auto view = cell->reg.view<Circle::s_ptr>();
    view.each([&max_points, &precision, &out, &fractured_array, &scaling, &error_code](Circle::s_ptr circle) {
      Polygon::s_ptr polygon = circle->to_polygon();
      if (max_points > 4 && polygon->points.size() > max_points) {
        polygon->fracture(max_points, precision, fractured_array);
        Polygon::s_ptr* a_item = fractured_array.data();
        for (uint64_t j = 0; j < fractured_array.size(); j++, a_item++) {
          Polygon::s_ptr p = *a_item;
          ErrorCode err = write_gds(p, out, scaling);
          if (err != ErrorCode::NoError)
            error_code = err;
          p = nullptr;
        }
        fractured_array.clear();
      } else {
        ErrorCode err = write_gds(polygon, out, scaling);
        if (err != ErrorCode::NoError)
          error_code = err;
      }
    });
  }

  {
    Array<Polygon::s_ptr> fractured_array = {};

    auto view = cell->reg.view<Ellipse::s_ptr>();
    view.each([&max_points, &precision, &out, &fractured_array, &scaling, &error_code](Ellipse::s_ptr circle) {
      Polygon::s_ptr polygon = circle->to_polygon();
      if (max_points > 4 && polygon->points.size() > max_points) {
        polygon->fracture(max_points, precision, fractured_array);
        Polygon::s_ptr* a_item = fractured_array.data();
        for (uint64_t j = 0; j < fractured_array.size(); j++, a_item++) {
          Polygon::s_ptr p = *a_item;
          ErrorCode err = write_gds(p, out, scaling);
          if (err != ErrorCode::NoError)
            error_code = err;
          p = nullptr;
        }
        fractured_array.clear();
      } else {
        ErrorCode err = write_gds(polygon, out, scaling);
        if (err != ErrorCode::NoError)
          error_code = err;
      }
    });
  }

  {
    Array<Polygon::s_ptr> fractured_array = {};

    auto view = cell->reg.view<Fan::s_ptr>();
    view.each([&max_points, &precision, &out, &fractured_array, &scaling, &error_code](Fan::s_ptr circle) {
      Polygon::s_ptr polygon = circle->to_polygon();
      if (max_points > 4 && polygon->points.size() > max_points) {
        polygon->fracture(max_points, precision, fractured_array);
        Polygon::s_ptr* a_item = fractured_array.data();
        for (uint64_t j = 0; j < fractured_array.size(); j++, a_item++) {
          Polygon::s_ptr p = *a_item;
          ErrorCode err = write_gds(p, out, scaling);
          if (err != ErrorCode::NoError)
            error_code = err;
          p = nullptr;
        }
        fractured_array.clear();
      } else {
        ErrorCode err = write_gds(polygon, out, scaling);
        if (err != ErrorCode::NoError)
          error_code = err;
      }
    });
  }

  {
    Array<Polygon::s_ptr> fractured_array = {};

    auto view = cell->reg.view<Rectangle::s_ptr>();

    view.each([&max_points, &precision, &out, &fractured_array, &scaling, &error_code](Rectangle::s_ptr rectangle) {
      Polygon::s_ptr polygon = rectangle->to_polygon();
      if (max_points > 4 && polygon->points.size() > max_points) {
        polygon->fracture(max_points, precision, fractured_array);
        Polygon::s_ptr* a_item = fractured_array.data();
        for (uint64_t j = 0; j < fractured_array.size(); j++, a_item++) {
          Polygon::s_ptr p = *a_item;
          ErrorCode err = write_gds(p, out, scaling);
          if (err != ErrorCode::NoError)
            error_code = err;
          p = nullptr;
        }
        fractured_array.clear();
      } else {
        ErrorCode err = write_gds(polygon, out, scaling);
        if (err != ErrorCode::NoError)
          error_code = err;
      }
    });
  }

  {
    Array<Polygon::s_ptr> fractured_array = {};

    auto view = cell->reg.view<Path::s_ptr>();

    view.each([&max_points, &precision, &out, &fractured_array, &scaling, &error_code](Path::s_ptr path) {
      auto flexpath = path;
      flexpath->simple_path = false;

      if (flexpath->elements.empty()) {
        flexpath->simple_path = true;

        auto& e = flexpath->elements.emplace_back();
        auto& width_and_offset = e.half_width_and_offset.emplace_back();

        width_and_offset.x = 0;
        width_and_offset.y = 0;
      }

      if (flexpath->elements[0].half_width_and_offset[0].x == 0) {
        flexpath->simple_path = true;
        flexpath->elements.resize(1);
      }

      if (flexpath->simple_path) {
        ErrorCode err = write_gds(flexpath, out, scaling);
        if (err != ErrorCode::NoError)
          error_code = err;
      } else {
        Array<Polygon::s_ptr> fp_array = {};
        ErrorCode err = flexpath->to_polygons(false, Tag{0, 0}, fp_array);
        if (err != ErrorCode::NoError)
          error_code = err;
        auto p_item = fp_array.data();
        for (uint64_t i = 0; i < fp_array.size(); i++, p_item++) {
          Polygon::s_ptr polygon = *p_item;
          if (max_points > 4 && polygon->points.size() > max_points) {
            polygon->fracture(max_points, precision, fractured_array);
            Polygon::s_ptr* a_item = fractured_array.data();
            for (uint64_t j = 0; j < fractured_array.size(); j++, a_item++) {
              Polygon::s_ptr p = *a_item;
              err = write_gds(p, out, scaling);
              if (err != ErrorCode::NoError)
                error_code = err;
              p = nullptr;
            }
            fractured_array.clear();
          } else {
            err = write_gds(polygon, out, scaling);
            if (err != ErrorCode::NoError)
              error_code = err;
          }
          polygon = nullptr;
        }
        fp_array.clear();
      }
    });
  }

  {
    auto view = cell->reg.view<Label::s_ptr>();

    view.each([&max_points, &precision, &out, &scaling, &error_code](Label::s_ptr label) {
      ErrorCode err = write_gds(label, out, scaling);
      if (err != ErrorCode::NoError)
        error_code = err;
    });
  }

  {
    auto view = cell->reg.view<KeyPoint::s_ptr>();

    view.each([&max_points, &precision, &out, &scaling, &error_code](KeyPoint::s_ptr keyPoint) {
      keyPoint->savePropertiesGDS();
      ErrorCode err = write_gds(keyPoint, out, scaling);
      if (err != ErrorCode::NoError)
        error_code = err;
    });
  }

  {
    auto view = cell->reg.view<Pin::s_ptr>();

    view.each([&max_points, &precision, &out, &scaling, &error_code](Pin::s_ptr pin) {
      pin->savePropertiesGDS();
      ErrorCode err = write_gds(pin, out, scaling);
      if (err != ErrorCode::NoError)
        error_code = err;
    });
  }

  {
    Array<Polygon::s_ptr> fractured_array = {};

    auto view = cell->reg.view<Text::s_ptr>();

    view.each([&max_points, &precision, &out, &fractured_array, &scaling, &error_code](Text::s_ptr text) {
      auto polygons = text->to_polygons();
      for (auto polygon : polygons) {
        if (max_points > 4 && polygon->points.size() > max_points) {
          polygon->fracture(max_points, precision, fractured_array);
          Polygon::s_ptr* a_item = fractured_array.data();
          for (uint64_t j = 0; j < fractured_array.size(); j++, a_item++) {
            Polygon::s_ptr p = *a_item;
            ErrorCode err = write_gds(p, out, scaling);
            if (err != ErrorCode::NoError)
              error_code = err;
            p = nullptr;
          }
          fractured_array.clear();
        } else {
          ErrorCode err = write_gds(polygon, out, scaling);
          if (err != ErrorCode::NoError)
            error_code = err;
        };
      }
    });
  }

  {
    auto view = cell->reg.view<Reference::s_ptr>();

    view.each([&max_points, &precision, &out, &scaling, &error_code](Reference::s_ptr refer) {
      ErrorCode err = write_gds(refer, out, scaling);
      if (err != ErrorCode::NoError)
        error_code = err;
    });
  }

  uint16_t buffer_end[] = {4, 0x0700};
  big_endian_swap16(buffer_end, COUNT(buffer_end));
  fwrite(buffer_end, sizeof(uint16_t), COUNT(buffer_end), out);
  return error_code;
}

ErrorCode DB::write_gds(const Library::s_ptr library, std::string filename, uint64_t max_points, tm* timestamp) {
  ErrorCode error_code = ErrorCode::NoError;
  FILE* out = fopen(filename.c_str(), "wb");
  if (out == NULL) {
    if (error_logger)
      fputs("[GDSTK] Unable to open GDSII file for output.\n", error_logger);
    return ErrorCode::OutputFileOpenError;
  }

  library->addReferCell();

  tm now = {};
  if (!timestamp)
    timestamp = get_now(now);

  uint64_t len = library->name.size();
  if (len % 2)
    len++;

  uint16_t buffer_start[] = {6,
                             0x0002,
                             0x0258,
                             28,
                             0x0102,
                             (uint16_t)(timestamp->tm_year + 1900),
                             (uint16_t)(timestamp->tm_mon + 1),
                             (uint16_t)timestamp->tm_mday,
                             (uint16_t)timestamp->tm_hour,
                             (uint16_t)timestamp->tm_min,
                             (uint16_t)timestamp->tm_sec,
                             (uint16_t)(timestamp->tm_year + 1900),
                             (uint16_t)(timestamp->tm_mon + 1),
                             (uint16_t)timestamp->tm_mday,
                             (uint16_t)timestamp->tm_hour,
                             (uint16_t)timestamp->tm_min,
                             (uint16_t)timestamp->tm_sec,
                             (uint16_t)(4 + len),
                             0x0206};
  big_endian_swap16(buffer_start, COUNT(buffer_start));
  fwrite(buffer_start, sizeof(uint16_t), COUNT(buffer_start), out);
  fwrite(library->name.c_str(), 1, len, out);

  uint16_t buffer_units[] = {20, 0x0305};
  big_endian_swap16(buffer_units, COUNT(buffer_units));
  fwrite(buffer_units, sizeof(uint16_t), COUNT(buffer_units), out);
  uint64_t units[] = {gdsii_real_from_double(library->precision / library->unit), gdsii_real_from_double(library->precision)};
  big_endian_swap64(units, COUNT(units));
  fwrite(units, sizeof(uint64_t), COUNT(units), out);

  double scaling = library->unit / library->precision;
  for (auto& cell : library->cells) {
    ErrorCode err = write_gds(cell, out, scaling, max_points, library->precision, timestamp);
    if (err != ErrorCode::NoError)
      error_code = err;
  }

  uint16_t buffer_end[] = {4, 0x0400};
  big_endian_swap16(buffer_end, COUNT(buffer_end));
  fwrite(buffer_end, sizeof(uint16_t), COUNT(buffer_end), out);

  fclose(out);
  return error_code;
}

Library::s_ptr DB::read_gds(std::string filename, double unit, double tolerance, ErrorCode* error_code) {
  const char* gdsii_record_names[] = {
      "HEADER",   "BGNLIB",   "LIBNAME",  "UNITS",        "ENDLIB",      "BGNSTR",    "STRNAME",   "ENDSTR",     "BOUNDARY", "PATH",
      "SREF",     "AREF",     "TEXT",     "LAYER",        "DATATYPE",    "WIDTH",     "XY",        "ENDEL",      "SNAME",    "COLROW",
      "TEXTNODE", "NODE",     "TEXTTYPE", "PRESENTATION", "SPACING",     "STRING",    "STRANS",    "MAG",        "ANGLE",    "UINTEGER",
      "USTRING",  "REFLIBS",  "FONTS",    "PATHTYPE",     "GENERATIONS", "ATTRTABLE", "STYPTABLE", "STRTYPE",    "ELFLAGS",  "ELKEY",
      "LINKTYPE", "LINKKEYS", "NODETYPE", "PROPATTR",     "PROPVALUE",   "BOX",       "BOXTYPE",   "PLEX",       "BGNEXTN",  "ENDEXTN",
      "TAPENUM",  "TAPECODE", "STRCLASS", "RESERVED",     "FORMAT",      "MASK",      "ENDMASKS",  "LIBDIRSIZE", "SRFNAME",  "LIBSECUR"};

  Library::s_ptr library = Library::create();

  uint8_t buffer[65537];
  int16_t* data16 = (int16_t*)(buffer + 4);
  int32_t* data32 = (int32_t*)(buffer + 4);
  uint64_t* data64 = (uint64_t*)(buffer + 4);
  char* str = (char*)(buffer + 4);

  Cell::s_ptr cell = NULL;
  Polygon::s_ptr polygon = NULL;
  Path::s_ptr path = NULL;
  Reference::s_ptr reference = NULL;
  Label::s_ptr label = NULL;

  double factor = 1;
  double width = 0;
  int16_t key = 0;

  FILE* in = fopen(filename.c_str(), "rb");
  if (in == NULL) {
    fputs("[GDSTK] Unable to open GDSII file for input.\n", stderr);
    if (error_code)
      *error_code = ErrorCode::InputFileOpenError;
    return library;
  }

  std::map<Cell::s_ptr, ReferencePtrArray> _reference_map;

  while (true) {
    uint64_t record_length = COUNT(buffer);
    ErrorCode err = gdsii_read_record(in, buffer, record_length);
    if (err != ErrorCode::NoError) {
      if (error_code)
        *error_code = err;
      break;
    }

    uint64_t data_length;
    switch ((GdsiiDataType)buffer[3]) {
      case GdsiiDataType::BitArray:
      case GdsiiDataType::TwoByteSignedInteger:
        data_length = (record_length - 4) / 2;
        big_endian_swap16((uint16_t*)data16, data_length);
        // for (uint32_t i = 0; i < data_length; i++) printf(" %" PRId16, data16[i]);
        break;
      case GdsiiDataType::FourByteSignedInteger:
      case GdsiiDataType::FourByteReal:
        data_length = (record_length - 4) / 4;
        big_endian_swap32((uint32_t*)data32, data_length);
        // for (uint32_t i = 0; i < data_length; i++) printf(" %" PRId32, data32[i]);
        break;
      case GdsiiDataType::EightByteReal:
        data_length = (record_length - 4) / 8;
        big_endian_swap64(data64, data_length);
        // for (uint32_t i = 0; i < data_length; i++)
        // printf(" %" PRIu64 " (%g)", data64[i], gdsii_real_to_double(data64[i]));
        break;
      default:
        data_length = record_length - 4;
        // for (uint32_t i = 0; i < data_length; i++) printf(" %c", str[i]);
    }

    // putchar('\n');

    switch ((GdsiiRecord)(buffer[2])) {
      case GdsiiRecord::HEADER:
      case GdsiiRecord::BGNLIB:
      case GdsiiRecord::ENDSTR:
        break;
      case GdsiiRecord::LIBNAME:
        if (str[data_length - 1] == 0)
          data_length--;
        library->name = std::string(str, data_length);
        break;
      case GdsiiRecord::UNITS: {
        const double db_in_user = gdsii_real_to_double(data64[0]);
        const double db_in_meters = gdsii_real_to_double(data64[1]);
        if (unit > 0) {
          factor = db_in_meters / unit;
          library->unit = unit;
        } else {
          factor = db_in_user;
          library->unit = db_in_meters / db_in_user;
        }
        library->precision = db_in_meters;
        if (tolerance <= 0) {
          tolerance = library->precision / library->unit;
        }
      } break;
      case GdsiiRecord::ENDLIB: {
        // 遍历Library cells属性
        Map<Cell::s_ptr> map;
        uint64_t c_size = library->cells.size();
        Cell::s_ptr* c_item = library->cells.data();

        // 统计cell name-ptr
        for (uint64_t i = c_size; i > 0; i--, c_item++)
          map[(*c_item)->name] = *c_item;

        // 遍历reference_map
        auto it_t = _reference_map.begin();
        while (it_t != _reference_map.end()) {
          auto& references = it_t->second;

          Reference::s_ptr* ref = references.data();
          for (uint64_t j = references.size(); j > 0; j--) {
            reference = *ref++;

            auto it = map.find(reference->name);
            Cell::s_ptr cp;
            if (it != map.end()) {
              cp = it->second;
              reference->name.clear();
              reference->type = ReferenceType::Cell;
              reference->cell = cp;
            } else {
              cp = nullptr;
              if (error_code)
                *error_code = ErrorCode::MissingReference;
              if (error_logger)
                fprintf(error_logger, "[GDSTK] Missing referenced cell %s\n", reference->name);
            }
          }

          it_t++;
        }
        map.clear();
        fclose(in);

        for (auto cell : library->cells) {
          cell->gdsToQLayout();
        }

        library->load();

        return library;
      } break;
      case GdsiiRecord::BGNSTR:
        cell = Cell::create();
        break;
      case GdsiiRecord::STRNAME:
        if (cell) {
          if (str[data_length - 1] == 0)
            data_length--;
          cell->name = std::string(str, data_length);
          library->cells.push_back(cell);
        }
        break;
      case GdsiiRecord::BOUNDARY:
      case GdsiiRecord::BOX:
        polygon = Polygon::create();
        cell->spawn(polygon);
        break;
      case GdsiiRecord::PATH:
      case GdsiiRecord::RAITHMBMSPATH:
        path = Path::create();
        cell->spawn(path);
        path->elements.resize(1);
        path->simple_path = true;
        break;
      case GdsiiRecord::RAITHPXXDATA:
        if (path) {
          PXXData pxxdata;
          memcpy(&pxxdata, buffer + 4, record_length);
          path->m_raith_data.from_pxxdata(pxxdata);
        }
        break;
      case GdsiiRecord::SREF:
      case GdsiiRecord::AREF:
        reference = Reference::create();
        reference->magnification = 1;
        if (cell) {
          entt::entity entity = cell->reg.create();
          cell->reg.emplace<Reference::s_ptr>(entity, reference);
          _reference_map[cell].push_back(reference);
        }
        break;
      case GdsiiRecord::TEXT:
        label = Label::create();
        cell->spawn(label);
        label->magnification = 1;
        break;
      case GdsiiRecord::LAYER:
        if (polygon) {
          polygon->layer = (data16[0] >= 0 ? data16[0] : (UINT16_MAX + data16[0] + 1));
        } else if (path) {
          path->layer = (data16[0] >= 0 ? data16[0] : (UINT16_MAX + data16[0] + 1));
        } else if (label) {
          label->layer = (data16[0] >= 0 ? data16[0] : (UINT16_MAX + data16[0] + 1));
        }
        break;
      case GdsiiRecord::DATATYPE:
      case GdsiiRecord::BOXTYPE:
        if (polygon) {
          polygon->datatype = data16[0];
        } else if (path) {
          path->datatype = data16[0];
        } else if (label) {
          label->datatype = data16[0];
        }
        break;
      case GdsiiRecord::WIDTH:
        if (data32[0] < 0) {
          width = factor * -data32[0];
          if (path)
            path->scale_width = false;
        } else {
          width = factor * data32[0];
          if (path)
            path->scale_width = true;
        }
        break;
      case GdsiiRecord::XY:
        if (polygon) {
          int old_size = polygon->points.size();
          vector_resize_now(polygon->points, data_length / 2);
          double* d = (double*)(polygon->points.data() + old_size);
          int32_t* s = data32;
          for (uint64_t i = data_length; i > 0; i--)
            *d++ = factor * (*s++);
        } else if (path) {
          Array<Vec2d> point_array = {};
          if (path->spine.points.size() == 0) {
            path->spine.tolerance = tolerance;
            path->spine.append(Vec2d{factor * data32[0], factor * data32[1]});
            path->elements[0].half_width_and_offset.push_back(Vec2d{width / 2, 0});
            path->width = width;
            vector_resize_now(point_array, data_length / 2 - 1);
            double* d = (double*)point_array.data();
            int32_t* s = data32 + 2;
            for (uint64_t i = data_length - 2; i > 0; i--)
              *d++ = factor * (*s++);
          } else {
            vector_resize_now(point_array, data_length / 2);
            double* d = (double*)point_array.data();
            int32_t* s = data32;
            for (uint64_t i = data_length; i > 0; i--)
              *d++ = factor * (*s++);
          }
          if (point_array.size() == 0)
            break;
          path->segment(point_array, NULL, NULL, false);
          point_array.clear();
        } else if (reference) {
          Vec2d origin = Vec2d{factor * data32[0], factor * data32[1]};
          reference->origin = origin;
          if (reference->repetition.type != RepetitionType::None) {
            Repetition* repetition = &reference->repetition;
            if (reference->rotation == 0 && !reference->x_reflection) {
              repetition->spacing.x = (factor * data32[2] - origin.x) / repetition->columns;
              repetition->spacing.y = (factor * data32[5] - origin.y) / repetition->rows;
            } else {
              repetition->type = RepetitionType::Regular;
              repetition->v1.x = (factor * data32[2] - origin.x) / repetition->columns;
              repetition->v1.y = (factor * data32[3] - origin.y) / repetition->columns;
              repetition->v2.x = (factor * data32[4] - origin.x) / repetition->rows;
              repetition->v2.y = (factor * data32[5] - origin.y) / repetition->rows;
            }
          }
        } else if (label) {
          label->origin.x = factor * data32[0];
          label->origin.y = factor * data32[1];
        }
        break;
      case GdsiiRecord::ENDEL:
        if (polygon) {
          Array<Vec2d>& pa = polygon->points;
          if (pa[0] == pa[pa.size() - 1])
            pa.pop_back();
        }
        polygon = NULL;
        path = NULL;
        reference = NULL;
        label = NULL;
        break;
      case GdsiiRecord::SNAME:
        if (reference) {
          if (str[data_length - 1] == 0)
            data_length--;
          reference->name = std::string(str, data_length);
          reference->type = ReferenceType::Name;
        } else if (path) {
          if (str[data_length - 1] == 0)
            data_length--;
          reference->name = std::string(str, data_length);
        }
        break;
      case GdsiiRecord::COLROW:
        if (reference) {
          Repetition* repetition = &reference->repetition;
          repetition->type = RepetitionType::Rectangular;
          repetition->columns = data16[0];
          repetition->rows = data16[1];
        }
        break;
      case GdsiiRecord::TEXTTYPE:
        if (label)
          label->datatype = data16[0];
        break;
      case GdsiiRecord::PRESENTATION:
        if (label)
          label->anchor = (Anchor)(data16[0] & 0x000F);
        break;
      case GdsiiRecord::STRING:
        if (label) {
          if (str[data_length - 1] == 0)
            data_length--;
          label->text = std::string(str, data_length);
        }
        break;
      case GdsiiRecord::STRANS:
        if (reference)
          reference->x_reflection = (data16[0] & 0x8000) != 0;
        else if (label)
          label->x_reflection = (data16[0] & 0x8000) != 0;
        if (data16[0] & 0x0006) {
          if (error_logger)
            fputs("[GDSTK] Absolute magnification and rotation of references is not supported.\n", error_logger);
          if (error_code)
            *error_code = ErrorCode::UnsupportedRecord;
        }
        break;
      case GdsiiRecord::MAG:
        if (reference)
          reference->magnification = gdsii_real_to_double(data64[0]);
        else if (label)
          label->magnification = gdsii_real_to_double(data64[0]);
        break;
      case GdsiiRecord::ANGLE:
        if (reference)
          reference->rotation = M_PI / 180.0 * gdsii_real_to_double(data64[0]);
        else if (label)
          label->rotation = M_PI / 180.0 * gdsii_real_to_double(data64[0]);
        break;
      case GdsiiRecord::PATHTYPE:
        if (path) {
          switch (data16[0]) {
            case 0:
              path->elements[0].end_type = EndType::Flush;
              break;
            case 1:
              path->elements[0].end_type = EndType::Round;
              break;
            case 2:
              path->elements[0].end_type = EndType::HalfWidth;
              break;
            default:
              path->elements[0].end_type = EndType::Extended;
          }
        }
        break;
      case GdsiiRecord::PROPATTR:
        key = data16[0];
        break;
      case GdsiiRecord::PROPVALUE:
        if (str[data_length - 1] != 0)
          str[data_length++] = 0;
        if (polygon) {
          set_gds_property(polygon->properties, key, str);
        } else if (path) {
          set_gds_property(path->properties, key, str);
        } else if (reference) {
          set_gds_property(reference->properties, key, str);
        } else if (label) {
          set_gds_property(label->properties, key, str);
          label->readProper();
        }
        break;
      case GdsiiRecord::BGNEXTN:
        if (path)
          path->elements[0].end_extensions.u = factor * data32[0];
        break;
      case GdsiiRecord::ENDEXTN:
        if (path)
          path->elements[0].end_extensions.v = factor * data32[0];
        break;
      default:
        if (buffer[2] < COUNT(gdsii_record_names)) {
          if (error_logger)
            fprintf(error_logger, "[GDSTK] Record type %s (0x%02X) is not supported.\n", gdsii_record_names[buffer[2]], buffer[2]);
        } else {
          if (error_logger)
            fprintf(error_logger, "[GDSTK] Unknown record type 0x%02X.\n", buffer[2]);
        }
        if (error_code)
          *error_code = ErrorCode::UnsupportedRecord;
    }
  }

  library = nullptr;
  fclose(in);
  return nullptr;
}

// inline void print_file(FILE* file) {
//   unsigned char buffer;
//   while (fread(&buffer, sizeof(buffer), 1, file)) {
//     // 以十六进制形式打印每个字节
//     std::cout << std::hex << static_cast<int>(buffer) << " ";
//   }
// }

// zlib memory management
static void* zalloc(void*, uInt count, uInt size) {
  return allocate(count * size);
}

static void zfree(void*, void* ptr) {
  free_allocation(ptr);
}

// // TODO: verify modal variables are correctly updated
Library::s_ptr DB::read_oas(const std::string& filename, double unit, double tolerance, ErrorCode* error_code) {
  // Library library;
  auto library = Library::create();

  OasisStream in = {};
  in.file = fopen(filename.c_str(), "rb");
  // print_file(in.file);

  if (in.file == NULL) {
    if (error_logger)
      fputs("[GDSTK] Unable to open OASIS file for input.\n", error_logger);
    if (error_code)
      *error_code = ErrorCode::InputFileOpenError;
    return library;
  }

  // Check header bytes and START record
  char header[14];
  if (fread(header, 1, 14, in.file) < 14 || memcmp(header, "%SEMI-OASIS\r\n\x01", 14) != 0) {
    if (error_logger)
      fputs("[GDSTK] Invalid OASIS header found.\n", error_logger);
    if (error_code)
      *error_code = ErrorCode::InvalidFile;
    fclose(in.file);
    return library;
  }

  // Process START record
  uint64_t len;
  uint8_t* version = oasis_read_string(in, false, len);
  if (in.error_code != ErrorCode::NoError) {
    if (error_code)
      *error_code = in.error_code;
    fclose(in.file);
    return library;
  }
  if (len != 3 || memcmp(version, "1.0", 3) != 0) {
    if (error_logger)
      fputs("[GDSTK] Unsupported OASIS file version.\n", error_logger);
    if (error_code)
      *error_code = ErrorCode::InvalidFile;
  }
  free_allocation(version);

  double factor = 1 / oasis_read_real(in);

  library->precision = 1e-6 * factor;
  if (unit > 0) {
    library->unit = unit;
    factor *= 1e-6 / unit;
  } else {
    library->unit = 1e-6;
  }
  if (tolerance <= 0) {
    tolerance = library->precision / library->unit;
  }

  uint64_t offset_table_flag = oasis_read_unsigned_integer(in);
  if (offset_table_flag == 0) {
    // Skip offset table
    for (uint8_t i = 12; i > 0; i--)
      oasis_read_unsigned_integer(in);
  }

  // State variables
  bool modal_absolute_pos = true;
  uint32_t modal_layer = 0;
  uint32_t modal_datatype = 0;
  uint32_t modal_textlayer = 0;
  uint32_t modal_texttype = 0;
  Vec2d modal_placement_pos = {0, 0};
  Vec2d modal_text_pos = {0, 0};
  Vec2d modal_geom_pos = {0, 0};
  Vec2d modal_geom_dim = {0, 0};
  Repetition modal_repetition = {RepetitionType::None};
  Label::s_ptr modal_text_string = NULL;
  Reference::s_ptr modal_placement_cell = NULL;
  Vec2dArray modal_polygon_points = {};
  modal_polygon_points.push_back(Vec2d{0, 0});
  Vec2dArray modal_path_points = {};
  modal_path_points.push_back(Vec2d{0, 0});
  double modal_path_halfwidth = 0;
  Vec2d modal_path_extensions = {0, 0};
  uint8_t modal_ctrapezoid_type = 0;
  double modal_circle_radius = 0;

  Property::s_ptr modal_property;
  PropertyValueArray modal_property_value_list;

  PropertyArray unfinished_property_name = {};
  PropertyValueArray unfinished_property_value = {};
  bool modal_property_unfinished = false;

  PropertyArray* next_property = &library->properties;

  // Name tables
  Array<StringArray> cell_name_table = {};
  Array<StringArray> label_text_table = {};
  Array<StringArray> property_name_table = {};
  Array<StringArray> property_value_table = {};

  TextPtrArray all_texts = {};

  // Elements
  Cell::s_ptr cell = nullptr;

  const char* oasis_record_names[] = {"PAD",
                                      "START",
                                      "END",
                                      "CELLNAME_IMPLICIT",
                                      "CELLNAME",
                                      "TEXTSTRING_IMPLICIT",
                                      "TEXTSTRING",
                                      "PROPNAME_IMPLICIT",
                                      "PROPNAME",
                                      "PROPSTRING_IMPLICIT",
                                      "PROPSTRING",
                                      "LAYERNAME_DATA",
                                      "LAYERNAME_TEXT",
                                      "CELL_REF_NUM",
                                      "CELL",
                                      "XYABSOLUTE",
                                      "XYRELATIVE",
                                      "PLACEMENT",
                                      "PLACEMENT_TRANSFORM",
                                      "TEXT",
                                      "RECTANGLE",
                                      "POLYGON",
                                      "PATH",
                                      "TRAPEZOID_AB",
                                      "TRAPEZOID_A",
                                      "TRAPEZOID_B",
                                      "CTRAPEZOID",
                                      "CIRCLE",
                                      "PROPERTY",
                                      "LAST_PROPERTY",
                                      "XNAME_IMPLICIT",
                                      "XNAME",
                                      "XELEMENT",
                                      "XGEOMETRY",
                                      "CBLOCK"};

  OasisRecord record;
  while ((error_code == NULL || *error_code == ErrorCode::NoError) && oasis_read(&record, 1, 1, in) == ErrorCode::NoError) {
    // DEBUG_PRINT("Record [%02u] %s\n", (uint8_t)record, (uint8_t)record < COUNT(oasis_record_names) ? oasis_record_names[(uint8_t)record] : "---");
    switch (record) {
      case OasisRecord::PAD:
        break;
      case OasisRecord::START:
        // START is parsed before this loop
        if (error_logger)
          fputs("[GDSTK] Unexpected START record out of position in file.\n", error_logger);
        if (error_code)
          *error_code = ErrorCode::InvalidFile;
        break;
      case OasisRecord::END: {
        FSEEK64(in.file, 0, SEEK_END);
        auto name = (char*)allocate(4);
        name[0] = 'L';
        name[1] = 'I';
        name[2] = 'B';
        name[3] = 0;
        std::string name_str(name);
        library->name = name_str;

        std::unordered_map<std::string, Cell::s_ptr> map;

        // 查表补全cell名称
        for (auto& cell : library->cells) {
          if (cell->name == "") {
            StringArray* cell_name = cell_name_table.data() + (uint64_t)cell->reference_number;
            // cell->owner = NULL;
            cell->name = cell_name->name;
            cell->properties = cell_name->properties;
          }
          map[cell->name] = cell;
        }

        // 查表补全cell里面label名称
        for (auto& cell : library->cells) {
          for (auto [e, label] : cell->reg.view<Label::s_ptr>().each()) {
            StringArray* label_text = label_text_table.data() + (uint64_t)label->reference_number;
            label->text = label_text->name;
          }
        }

        // 根据引用的cell名称编号映射引用指向的cell对象
        for (auto& cell : library->cells) {
          for (auto& ref : cell->oas_references) {
            if (ref->type == ReferenceType::Cell) {
              // 使用参考编号
              // StringArray* cell_name = cell_name_table.data() + (uint64_t)ref->cell.get();
              StringArray* cell_name = cell_name_table.data() + ref->reference_number;
              ref->cell = map[cell_name->name];
              if (!ref->cell) {
                ref->type = ReferenceType::Name;
                ref->name = cell_name->name;
                if (error_code)
                  *error_code = ErrorCode::MissingReference;
                if (error_logger)
                  fprintf(error_logger, "[GDSTK] Missing referenced cell %s\n", ref->name);
              }
            } else {
              // 使用名称
              cell = map[ref->name];
              if (cell) {
                ref->cell = cell;
                ref->type = ReferenceType::Cell;
              } else {
                if (error_code)
                  *error_code = ErrorCode::MissingReference;
                if (error_logger)
                  fprintf(error_logger, "[GDSTK] Missing referenced cell %s\n", ref->name);
              }
            }
          }
        }
        map.clear();

        // oas引用列表转换为map存储，清除引用数据
        for (auto& cell : library->cells) {
          for (auto& ref : cell->oas_references) {
            // cell->reference_map[ref->cell].push_back(ref);
            cell->spawn(ref);
          }
          cell->oas_references.clear();
        }

        // 查表补全属性名称
        for (auto it : unfinished_property_name) {
          auto p = property_name_table.data() + it->reference_number;
          it->name = p->name;
        }

        // 查表补全属值名称
        for (auto it : unfinished_property_value) {
          auto p = property_value_table.data() + it->reference_number;
          it->type = PropertyType::String;
          it->string = p->name;
        }

        // 筛选区分text、label
        for (auto& cell : library->cells) {
          // for (auto [e, ref] : cell->reg.view<Reference::s_ptr>().each()) {
          //   ref->setInstanceName(cell->gen_instance_name());
          // }

          // pcell属性处理
          auto type = get_property(cell->properties, "type");
          if (type) {
            cell->m_cell_type = (CellType)get_property(cell->properties, "type")->back()->unsigned_integer;
            cell->m_pcell_code = get_property(cell->properties, "pcell_code")->back()->string;
            auto pcell_svg_paths = get_property(cell->properties, "pcell_svg_paths");
            if (pcell_svg_paths) {
              for (auto& item : *pcell_svg_paths) {
                cell->m_dimension_diagrams.push_back(item->string);
              }
            }
          }

          // 骨架线、共面波导
          for (auto [e, path] : cell->reg.view<Path::s_ptr>().each()) {
            auto type = get_property(path->properties, "type");
            auto radius = get_property(path->properties, "radius");
            auto center_width = get_property(path->properties, "center_width");
            if (type) {
              path->type = (PathType)type->back()->unsigned_integer;
            }
            if (radius) {
              path->radius = (double)radius->back()->real;
            }
            if (center_width) {
              path->center_width = (double)center_width->back()->real;
            }
          }

          for (auto [e, rect] : cell->reg.view<Rectangle::s_ptr>().each()) {
            auto rotation = get_property(rect->properties, "rotation");
            if (rotation) {
              rect->rotation = rotation->back()->real;
            }
          }

          // 圆形、扇形、椭圆
          for (auto [e, circle] : cell->reg.view<Circle::s_ptr>().each()) {
            auto type_ps = get_property(circle->properties, "type");
            if (type_ps) {
              auto type = (OasisCircleType)type_ps->back()->unsigned_integer;
              switch (type) {
                case OasisCircleType::Circle: {
                } break;
                case OasisCircleType::Ellipse: {
                  auto rotation = get_property(circle->properties, "rotation")->back()->real;
                  auto radius_y = get_property(circle->properties, "radius_y")->back()->real;
                  auto ellipse = Ellipse::create(circle->center, Vec2d{circle->radius, radius_y}, rotation, 1e-3, circle->layer, circle->datatype);
                  cell->spawn(ellipse);
                  cell->reg.remove<Circle::s_ptr>(e);
                } break;
                case OasisCircleType::Fan: {
                  auto rotation = get_property(circle->properties, "rotation")->back()->real;
                  auto angle_start = get_property(circle->properties, "angle_start")->back()->real;
                  auto angle_end = get_property(circle->properties, "angle_end")->back()->real;
                  auto fan =
                      Fan::create(circle->center, circle->radius, Vec2d{angle_start, angle_end}, rotation, 1e-3, circle->layer, circle->datatype);
                  cell->spawn(fan);
                  cell->reg.remove<Circle::s_ptr>(e);
                } break;

                default:
                  break;
              }
            }
          }

          // 筛选区分text、label
          std::vector<entt::entity> label_e_arr;
          for (auto [e, label] : cell->reg.view<Label::s_ptr>().each()) {
            if (get_property(label->properties, "type")) {
              auto type = (OasisTextType)get_property(label->properties, "type")->back()->unsigned_integer;

              switch (type) {
                case OasisTextType::Label: {
                  auto size = get_property(label->properties, "size")->back()->real;
                  auto anchor = (Anchor)get_property(label->properties, "anchor")->back()->unsigned_integer;
                  auto rotation = get_property(label->properties, "rotation")->back()->real;
                  auto x_reflection = (bool)get_property(label->properties, "x_reflection")->back()->unsigned_integer;
                  auto magnification = get_property(label->properties, "magnification")->back()->real;
                  auto item = Label::create(label->text, label->origin, rotation, magnification, x_reflection, label->layer, label->datatype);
                  Property::s_ptr find_pro = find_property(label->properties, "gds_7");
                  if (find_pro != nullptr) {
                    auto gds_7 = get_property(label->properties, "gds_7")->back()->string;
                    set_gds_property(item->properties, 7, gds_7);
                  }

                  item->anchor = anchor;
                  item->fontSize = size;
                  cell->spawn(item);
                } break;
                case OasisTextType::Text: {
                  auto size = get_property(label->properties, "size")->back()->real;
                  auto anchor = (Anchor)get_property(label->properties, "anchor")->back()->unsigned_integer;
                  auto rotation = get_property(label->properties, "rotation")->back()->real;
                  auto x_reflection = (bool)get_property(label->properties, "x_reflection")->back()->unsigned_integer;
                  auto magnification = get_property(label->properties, "magnification")->back()->real;

                  auto item = Text::create(label->text, label->origin, rotation, magnification, x_reflection, label->layer, label->datatype);
                  item->anchor = anchor;
                  item->fontSize = size;
                  cell->spawn(item);
                } break;
                case OasisTextType::Pin: {
                  auto size = get_property(label->properties, "size")->back()->real;
                  auto anchor = (Anchor)get_property(label->properties, "anchor")->back()->unsigned_integer;
                  auto rotation = get_property(label->properties, "rotation")->back()->real;
                  auto x_reflection = (bool)get_property(label->properties, "x_reflection")->back()->unsigned_integer;
                  auto magnification = get_property(label->properties, "magnification")->back()->real;

                  auto item = Pin::create(label->text, label->origin, rotation, magnification, x_reflection, label->layer, label->datatype);
                  item->anchor = anchor;
                  item->fontSize = size;
                  cell->spawn(item);
                } break;
                case OasisTextType::KeyPoint: {
                  auto size = get_property(label->properties, "size")->back()->real;
                  auto anchor = (Anchor)get_property(label->properties, "anchor")->back()->unsigned_integer;
                  auto rotation = get_property(label->properties, "rotation")->back()->real;
                  auto x_reflection = (bool)get_property(label->properties, "x_reflection")->back()->unsigned_integer;
                  auto magnification = get_property(label->properties, "magnification")->back()->real;
                  auto haveLabel = (bool)get_property(label->properties, "haveLabel")->back()->unsigned_integer;

                  auto item = KeyPoint::create(label->text, label->origin, rotation, magnification, x_reflection, label->layer, label->datatype);
                  item->anchor = anchor;
                  item->fontSize = size;
                  item->haveLabel = haveLabel;
                  cell->spawn(item);
                } break;

                default:
                  break;
              }
            }
            // 删除label及label的tag，防止自动生成图层编号问题
            label_e_arr.push_back(e);
            // cell->reg.remove<Label::s_ptr, qlib::Tag>(e);
          }
          // 删除label
          for (auto& e : label_e_arr) {
            cell->reg.destroy(e);
          }
        }

        library->load();

        goto CLEANUP;
      } break;
      case OasisRecord::CELLNAME_IMPLICIT: {
        std::string str((char*)oasis_read_string(in, true, len));
        cell_name_table.push_back(StringArray{str});
        next_property = &cell_name_table[cell_name_table.size() - 1].properties;
      } break;
      case OasisRecord::CELLNAME: {
        std::string str((char*)oasis_read_string(in, true, len));
        uint64_t ref_number = oasis_read_unsigned_integer(in);
        if (ref_number >= cell_name_table.size()) {
          auto size = cell_name_table.size();
          auto expend_size = ref_number + 1 - size;
          if (size < size + expend_size)
            cell_name_table.resize(size + expend_size);
          for (uint64_t i = size; i < ref_number; i++) {
            cell_name_table[i] = StringArray{};
          }
          cell_name_table.resize(ref_number + 1);
        }
        cell_name_table[ref_number] = StringArray{str};
        next_property = &cell_name_table[ref_number].properties;
      } break;
      case OasisRecord::TEXTSTRING_IMPLICIT: {
        std::string str((char*)oasis_read_string(in, true, len));
        label_text_table.push_back(StringArray{str});
        next_property = &label_text_table[label_text_table.size() - 1].properties;
      } break;
      case OasisRecord::TEXTSTRING: {
        std::string str((char*)oasis_read_string(in, true, len));
        uint64_t ref_number = oasis_read_unsigned_integer(in);
        if (ref_number >= label_text_table.size()) {
          auto size = label_text_table.size();
          auto expend_size = ref_number + 1 - size;
          if (size < size + expend_size)
            label_text_table.resize(size + expend_size);
          for (uint64_t i = size; i < ref_number; i++) {
            label_text_table[i] = StringArray{};
          }
          label_text_table.resize(ref_number + 1);
        }
        label_text_table[ref_number] = StringArray{str};
        next_property = &label_text_table[ref_number].properties;
      } break;
      case OasisRecord::PROPNAME_IMPLICIT: {
        std::string str((char*)oasis_read_string(in, true, len));
        property_name_table.push_back(StringArray{str});
        next_property = &property_name_table[property_name_table.size() - 1].properties;
      } break;
      case OasisRecord::PROPNAME: {
        std::string str((char*)oasis_read_string(in, true, len));
        uint64_t ref_number = oasis_read_unsigned_integer(in);
        if (ref_number >= property_name_table.size()) {
          auto size = property_name_table.size();
          auto expend_size = ref_number + 1 - size;
          if (size < size + expend_size)
            property_name_table.resize(size + expend_size);
          for (uint64_t i = size; i < ref_number; i++) {
            property_name_table[i] = StringArray{};
          }
          property_name_table.resize(ref_number + 1);
        }
        property_name_table[ref_number] = StringArray{str};
        next_property = &property_name_table[ref_number].properties;
      } break;
      case OasisRecord::PROPSTRING_IMPLICIT: {
        std::string str((char*)oasis_read_string(in, true, len));
        property_value_table.push_back(StringArray{str});
        next_property = &property_value_table[property_value_table.size() - 1].properties;
      } break;
      case OasisRecord::PROPSTRING: {
        std::string str((char*)oasis_read_string(in, true, len));
        uint64_t ref_number = oasis_read_unsigned_integer(in);
        if (ref_number >= property_value_table.size()) {
          auto size = property_value_table.size();
          auto expend_size = ref_number + 1 - size;
          if (size < size + expend_size)
            property_value_table.resize(size + expend_size);
          for (uint64_t i = size; i < ref_number; i++) {
            property_value_table[i] = StringArray{};
          }
          property_value_table.resize(ref_number + 1);
        }
        property_value_table[ref_number] = StringArray{str};
        next_property = &property_value_table[ref_number].properties;
      } break;
      case OasisRecord::LAYERNAME_DATA:
      case OasisRecord::LAYERNAME_TEXT:
        // Unused record
        free_allocation(oasis_read_string(in, false, len));
        for (uint32_t i = 2; i > 0; i--) {
          uint64_t type = oasis_read_unsigned_integer(in);
          if (type > 0) {
            if (type == 4)
              oasis_read_unsigned_integer(in);
            oasis_read_unsigned_integer(in);
          }
        }
        break;
      case OasisRecord::CELL_REF_NUM:
      case OasisRecord::CELL: {
        cell = Cell::create();
        library->cells.push_back(cell);
        next_property = &cell->properties;
        if (record == OasisRecord::CELL_REF_NUM) {
          // Use owner as temporary storage for the reference number
          cell->reference_number = oasis_read_unsigned_integer(in);
        } else {
          std::string str((char*)oasis_read_string(in, true, len));
          cell->name = str;
        }
        if (cell->reference_number == 10) {
          std::cout << "10" << std::endl;
        }

        modal_absolute_pos = true;
        modal_placement_pos = Vec2d{0, 0};
        modal_geom_pos = Vec2d{0, 0};
        modal_text_pos = Vec2d{0, 0};
      } break;
      case OasisRecord::XYABSOLUTE:
        modal_absolute_pos = true;
        break;
      case OasisRecord::XYRELATIVE:
        modal_absolute_pos = false;
        break;
      case OasisRecord::PLACEMENT:
      case OasisRecord::PLACEMENT_TRANSFORM: {
        auto reference = Reference::create();
        cell->oas_references.push_back(reference);
        next_property = &reference->properties;
        uint8_t info;
        oasis_read(&info, 1, 1, in);
        if (info & 0x80) {
          // 明确reference
          if (info & 0x40) {
            // 参考编号
            reference->type = ReferenceType::Cell;
            reference->reference_number = oasis_read_unsigned_integer(in);
          } else {
            // Cell name
            reference->type = ReferenceType::Name;
            std::string str((char*)oasis_read_string(in, true, len));
            reference->name = str;
          }
          modal_placement_cell = reference;
        } else {
          // 使用模态变量
          if (modal_placement_cell->type == ReferenceType::Cell) {
            reference->type = ReferenceType::Cell;
            // reference->cell = modal_placement_cell->cell;
            reference->reference_number = modal_placement_cell->reference_number;
          } else {
            reference->type = ReferenceType::Name;
            reference->name = modal_placement_cell->name;
          }
        }
        if (record == OasisRecord::PLACEMENT) {
          reference->magnification = 1;
          switch (info & 0x06) {
            case 0x02:
              reference->rotation = M_PI * 0.5;
              break;
            case 0x04:
              reference->rotation = M_PI;
              break;
            case 0x06:
              reference->rotation = M_PI * 1.5;
          }
        } else {
          if (info & 0x04) {
            reference->magnification = oasis_read_real(in);
          } else {
            reference->magnification = 1;
          }
          if (info & 0x02) {
            reference->rotation = oasis_read_real(in) * (M_PI / 180.0);
          }
        }
        reference->x_reflection = info & 0x01;
        if (info & 0x20) {
          double x = factor * oasis_read_integer(in);
          if (modal_absolute_pos) {
            modal_placement_pos.x = x;
          } else {
            modal_placement_pos.x += x;
          }
        }
        if (info & 0x10) {
          double y = factor * oasis_read_integer(in);
          if (modal_absolute_pos) {
            modal_placement_pos.y = y;
          } else {
            modal_placement_pos.y += y;
          }
        }
        reference->origin = modal_placement_pos;
        if (info & 0x08) {
          oasis_read_repetition(in, factor, modal_repetition);
          reference->repetition = modal_repetition;
        }
        // cell->spawn(reference);
        // cell->add(reference);
      } break;
      case OasisRecord::TEXT: {
        auto label = Label::create();
        // cell->labels.push_back(label);
        next_property = &label->properties;

        label->magnification = 1;
        label->anchor = Anchor::SW;
        uint8_t info;
        oasis_read(&info, 1, 1, in);
        if (info & 0x40) {
          // Explicit text
          if (info & 0x20) {
            // 参考编号: 用于查表获取text内容
            label->reference_number = oasis_read_unsigned_integer(in);
          } else {
            label->text = (char*)oasis_read_string(in, true, len);
          }
          modal_text_string = label;
        } else {
          // Use modal_text_string
          if (modal_text_string->text == "") {
            label->owner = modal_text_string->owner;
          } else {
            label->text = modal_text_string->text;
          }
        }
        if (info & 0x01) {
          modal_textlayer = (uint32_t)oasis_read_unsigned_integer(in);
        }
        label->layer = modal_textlayer;
        if (info & 0x02) {
          modal_texttype = (uint32_t)oasis_read_unsigned_integer(in);
        }
        label->datatype = modal_texttype;
        if (info & 0x10) {
          double x = factor * oasis_read_integer(in);
          if (modal_absolute_pos) {
            modal_text_pos.x = x;
          } else {
            modal_text_pos.x += x;
          }
        }
        if (info & 0x08) {
          double y = factor * oasis_read_integer(in);
          if (modal_absolute_pos) {
            modal_text_pos.y = y;
          } else {
            modal_text_pos.y += y;
          }
        }
        label->origin = modal_text_pos;
        if (info & 0x04) {
          oasis_read_repetition(in, factor, modal_repetition);
          label->repetition = modal_repetition;
        }

        cell->spawn(label);
        // all_texts.push_back(label);
      } break;
      case OasisRecord::RECTANGLE: {
        uint8_t info;
        oasis_read(&info, 1, 1, in);
        if (info & 0x01) {
          modal_layer = (uint32_t)oasis_read_unsigned_integer(in);
        }
        if (info & 0x02) {
          modal_datatype = (uint32_t)oasis_read_unsigned_integer(in);
        }
        if (info & 0x40) {
          modal_geom_dim.x = factor * oasis_read_unsigned_integer(in);
        }
        if (info & 0x20) {
          modal_geom_dim.y = factor * oasis_read_unsigned_integer(in);
        } else if (info & 0x80) {
          modal_geom_dim.y = modal_geom_dim.x;
        }
        if (info & 0x10) {
          double x = factor * oasis_read_integer(in);
          if (modal_absolute_pos) {
            modal_geom_pos.x = x;
          } else {
            modal_geom_pos.x += x;
          }
        }
        if (info & 0x08) {
          double y = factor * oasis_read_integer(in);
          if (modal_absolute_pos) {
            modal_geom_pos.y = y;
          } else {
            modal_geom_pos.y += y;
          }
        }
        auto rectangle = Rectangle::create();
        rectangle->init(modal_geom_pos, modal_geom_pos + modal_geom_dim, modal_layer, modal_datatype);
        next_property = &rectangle->properties;
        if (info & 0x04) {
          oasis_read_repetition(in, factor, modal_repetition);
          rectangle->repetition = modal_repetition;
        }
        cell->spawn(rectangle);
      } break;
      case OasisRecord::POLYGON: {
        auto polygon = Polygon::create();
        // cell->polygons.push_back(polygon);
        next_property = &polygon->properties;
        uint8_t info;
        oasis_read(&info, 1, 1, in);
        if (info & 0x01) {
          modal_layer = (uint32_t)oasis_read_unsigned_integer(in);
        }
        polygon->layer = modal_layer;
        if (info & 0x02) {
          modal_datatype = (uint32_t)oasis_read_unsigned_integer(in);
        }
        polygon->datatype = modal_datatype;
        if (info & 0x20) {
          modal_polygon_points.resize(1);
          oasis_read_point_list(in, factor, true, modal_polygon_points);
        }
        polygon->points = modal_polygon_points;
        if (info & 0x10) {
          double x = factor * oasis_read_integer(in);
          if (modal_absolute_pos) {
            modal_geom_pos.x = x;
          } else {
            modal_geom_pos.x += x;
          }
        }
        if (info & 0x08) {
          double y = factor * oasis_read_integer(in);
          if (modal_absolute_pos) {
            modal_geom_pos.y = y;
          } else {
            modal_geom_pos.y += y;
          }
        }
        Vec2d* v = polygon->points.data();
        for (uint64_t i = polygon->points.size(); i > 0; i--) {
          *v++ += modal_geom_pos;
        }
        if (info & 0x04) {
          oasis_read_repetition(in, factor, modal_repetition);
          polygon->repetition = modal_repetition;
        }
        cell->spawn(polygon);
      } break;
      case OasisRecord::PATH: {
        auto path = Path::create();
        // cell->paths.push_back(path);
        next_property = &path->properties;

        // path->elements.resize(1);
        // auto element = &path->elements[0];

        path->spine.tolerance = tolerance;
        path->simple_path = true;
        path->scale_width = true;
        uint8_t info;
        oasis_read(&info, 1, 1, in);
        if (info & 0x01) {
          modal_layer = (uint32_t)oasis_read_unsigned_integer(in);
        }
        path->layer = modal_layer;
        if (info & 0x02) {
          modal_datatype = (uint32_t)oasis_read_unsigned_integer(in);
        }
        path->datatype = modal_datatype;
        if (info & 0x40) {
          modal_path_halfwidth = factor * oasis_read_unsigned_integer(in);
        }
        path->width = modal_path_halfwidth;
        if (info & 0x80) {
          uint8_t extension_scheme;
          oasis_read(&extension_scheme, 1, 1, in);
          switch (extension_scheme & 0x0c) {
            case 0x04:
              modal_path_extensions.u = 0;
              break;
            case 0x08:
              modal_path_extensions.u = modal_path_halfwidth;
              break;
            case 0x0c:
              modal_path_extensions.u = factor * oasis_read_integer(in);
          }
          switch (extension_scheme & 0x03) {
            case 0x01:
              modal_path_extensions.v = 0;
              break;
            case 0x02:
              modal_path_extensions.v = modal_path_halfwidth;
              break;
            case 0x03:
              modal_path_extensions.v = factor * oasis_read_integer(in);
          }
        }
        if (modal_path_extensions.u == 0 && modal_path_extensions.v == 0) {
          path->end_type = EndType::Flush;
        } else if (modal_path_extensions.u == modal_path_halfwidth && modal_path_extensions.v == modal_path_halfwidth) {
          path->end_type = EndType::HalfWidth;
        } else {
          path->end_type = EndType::Extended;
          path->end_extensions = modal_path_extensions;
        }
        if (info & 0x20) {
          modal_path_points.resize(1);
          oasis_read_point_list(in, factor, false, modal_path_points);
        }
        if (info & 0x10) {
          double x = factor * oasis_read_integer(in);
          if (modal_absolute_pos) {
            modal_geom_pos.x = x;
          } else {
            modal_geom_pos.x += x;
          }
        }
        if (info & 0x08) {
          double y = factor * oasis_read_integer(in);
          if (modal_absolute_pos) {
            modal_geom_pos.y = y;
          } else {
            modal_geom_pos.y += y;
          }
        }
        path->spine.append(modal_geom_pos);
        // const Vec2dArray skip_first = {0, modal_path_points.size() - 1, modal_path_points.data() + 1};
        Vec2dArray skip_first(modal_path_points.begin() + 1, modal_path_points.end());
        if (skip_first.size() > 0)
          path->segment(skip_first, NULL, NULL, true);
        if (info & 0x04) {
          oasis_read_repetition(in, factor, modal_repetition);
          path->repetition = modal_repetition;
        }

        cell->spawn(path);
      } break;
      case OasisRecord::TRAPEZOID_AB:
      case OasisRecord::TRAPEZOID_A:
      case OasisRecord::TRAPEZOID_B: {
        // Polygon* polygon = (Polygon*)allocate_clear(sizeof(Polygon));
        // cell->polygon_array.append(polygon);
        // next_property = &polygon->properties;
        uint8_t info;
        oasis_read(&info, 1, 1, in);
        if (info & 0x01) {
          modal_layer = (uint32_t)oasis_read_unsigned_integer(in);
        }
        // set_layer(polygon->tag, modal_layer);
        if (info & 0x02) {
          modal_datatype = (uint32_t)oasis_read_unsigned_integer(in);
        }
        // set_type(polygon->tag, modal_datatype);
        if (info & 0x40) {
          modal_geom_dim.x = factor * oasis_read_unsigned_integer(in);
        }
        if (info & 0x20) {
          modal_geom_dim.y = factor * oasis_read_unsigned_integer(in);
        }
        double delta_a, delta_b;
        if (record == OasisRecord::TRAPEZOID_AB) {
          delta_a = factor * oasis_read_1delta(in);
          delta_b = factor * oasis_read_1delta(in);
        } else if (record == OasisRecord::TRAPEZOID_A) {
          delta_a = factor * oasis_read_1delta(in);
          delta_b = 0;
        } else {
          delta_a = 0;
          delta_b = factor * oasis_read_1delta(in);
        }
        if (info & 0x10) {
          double x = factor * oasis_read_integer(in);
          if (modal_absolute_pos) {
            modal_geom_pos.x = x;
          } else {
            modal_geom_pos.x += x;
          }
        }
        if (info & 0x08) {
          double y = factor * oasis_read_integer(in);
          if (modal_absolute_pos) {
            modal_geom_pos.y = y;
          } else {
            modal_geom_pos.y += y;
          }
        }
        // Array<Vec2>* point_array = &polygon->point_array;
        // point_array->ensure_slots(4);
        // point_array->count = 4;
        // Vec2* r = point_array->items;
        // Vec2* s = r + 1;
        // Vec2* q = s + 1;
        // Vec2* p = q + 1;
        // if (info & 0x80) {
        //   p->x = q->x = modal_geom_pos.x;
        //   r->x = s->x = modal_geom_pos.x + modal_geom_dim.x;
        //   if (delta_a < 0) {
        //     p->y = modal_geom_pos.y;
        //     r->y = p->y - delta_a;
        //   } else {
        //     r->y = modal_geom_pos.y;
        //     p->y = r->y + delta_a;
        //   }
        //   if (delta_b < 0) {
        //     s->y = modal_geom_pos.y + modal_geom_dim.y;
        //     q->y = s->y + delta_b;
        //   } else {
        //     q->y = modal_geom_pos.y + modal_geom_dim.y;
        //     s->y = q->y - delta_b;
        //   }
        // } else {
        //   p->y = q->y = modal_geom_pos.y + modal_geom_dim.y;
        //   r->y = s->y = modal_geom_pos.y;
        //   if (delta_a < 0) {
        //     p->x = modal_geom_pos.x;
        //     r->x = p->x - delta_a;
        //   } else {
        //     r->x = modal_geom_pos.x;
        //     p->x = r->x + delta_a;
        //   }
        //   if (delta_b < 0) {
        //     s->x = modal_geom_pos.x + modal_geom_dim.x;
        //     q->x = s->x + delta_b;
        //   } else {
        //     q->x = modal_geom_pos.x + modal_geom_dim.x;
        //     s->x = q->x - delta_b;
        //   }
        // }
        // if (info & 0x04) {
        //   oasis_read_repetition(in, factor, modal_repetition);
        //   polygon->repetition.copy_from(modal_repetition);
        // }
      } break;
      case OasisRecord::CTRAPEZOID: {
        // Polygon* polygon = (Polygon*)allocate_clear(sizeof(Polygon));
        // cell->polygon_array.append(polygon);
        // next_property = &polygon->properties;
        // uint8_t info;
        // oasis_read(&info, 1, 1, in);
        // if (info & 0x01) {
        //   modal_layer = (uint32_t)oasis_read_unsigned_integer(in);
        // }
        // set_layer(polygon->tag, modal_layer);
        // if (info & 0x02) {
        //   modal_datatype = (uint32_t)oasis_read_unsigned_integer(in);
        // }
        // set_type(polygon->tag, modal_datatype);
        // if (info & 0x80) {
        //   oasis_read(&modal_ctrapezoid_type, 1, 1, in);
        // }
        // if (info & 0x40) {
        //   modal_geom_dim.x = factor * oasis_read_unsigned_integer(in);
        // }
        // if (info & 0x20) {
        //   modal_geom_dim.y = factor * oasis_read_unsigned_integer(in);
        // }
        // if (info & 0x10) {
        //   double x = factor * oasis_read_integer(in);
        //   if (modal_absolute_pos) {
        //     modal_geom_pos.x = x;
        //   } else {
        //     modal_geom_pos.x += x;
        //   }
        // }
        // if (info & 0x08) {
        //   double y = factor * oasis_read_integer(in);
        //   if (modal_absolute_pos) {
        //     modal_geom_pos.y = y;
        //   } else {
        //     modal_geom_pos.y += y;
        //   }
        // }
        // Array<Vec2>* point_array = &polygon->point_array;
        // Vec2* v;
        // if (modal_ctrapezoid_type > 15 && modal_ctrapezoid_type < 24) {
        //   point_array->ensure_slots(3);
        //   point_array->count = 3;
        //   v = point_array->items;
        //   v[0] = modal_geom_pos;
        //   v[1] = modal_geom_pos;
        //   v[2] = modal_geom_pos;
        // } else {
        //   point_array->ensure_slots(4);
        //   point_array->count = 4;
        //   v = point_array->items;
        //   v[0] = modal_geom_pos;
        //   v[1] = modal_geom_pos + Vec2{modal_geom_dim.x, 0};
        //   v[2] = modal_geom_pos + modal_geom_dim;
        //   v[3] = modal_geom_pos + Vec2{0, modal_geom_dim.y};
        // }
        // switch (modal_ctrapezoid_type) {
        //   case 0:
        //     v[2].x -= modal_geom_dim.y;
        //     break;
        //   case 1:
        //     v[1].x -= modal_geom_dim.y;
        //     break;
        //   case 2:
        //     v[3].x += modal_geom_dim.y;
        //     break;
        //   case 3:
        //     v[0].x += modal_geom_dim.y;
        //     break;
        //   case 4:
        //     v[2].x -= modal_geom_dim.y;
        //     v[3].x += modal_geom_dim.y;
        //     break;
        //   case 5:
        //     v[0].x += modal_geom_dim.y;
        //     v[1].x -= modal_geom_dim.y;
        //     break;
        //   case 6:
        //     v[1].x -= modal_geom_dim.y;
        //     v[3].x += modal_geom_dim.y;
        //     break;
        //   case 7:
        //     v[0].x += modal_geom_dim.y;
        //     v[2].x -= modal_geom_dim.y;
        //     break;
        //   case 8:
        //     v[2].y -= modal_geom_dim.x;
        //     break;
        //   case 9:
        //     v[3].y -= modal_geom_dim.x;
        //     break;
        //   case 10:
        //     v[1].y += modal_geom_dim.x;
        //     break;
        //   case 11:
        //     v[0].y += modal_geom_dim.x;
        //     break;
        //   case 12:
        //     v[1].y += modal_geom_dim.x;
        //     v[2].y -= modal_geom_dim.x;
        //     break;
        //   case 13:
        //     v[0].y += modal_geom_dim.x;
        //     v[3].y -= modal_geom_dim.x;
        //     break;
        //   case 14:
        //     v[1].y += modal_geom_dim.x;
        //     v[3].y -= modal_geom_dim.x;
        //     break;
        //   case 15:
        //     v[0].y += modal_geom_dim.x;
        //     v[2].y -= modal_geom_dim.x;
        //     break;
        //   case 16:
        //     v[1].x += modal_geom_dim.x;
        //     v[2].y += modal_geom_dim.x;
        //     break;
        //   case 17:
        //     v[1] += modal_geom_dim.x;
        //     v[2].y += modal_geom_dim.x;
        //     break;
        //   case 18:
        //     v[1].x += modal_geom_dim.x;
        //     v[2] += modal_geom_dim.x;
        //     break;
        //   case 19:
        //     v[0].x += modal_geom_dim.x;
        //     v[1] += modal_geom_dim.x;
        //     v[2].y += modal_geom_dim.x;
        //     break;
        //   case 20:
        //     v[1].x += 2 * modal_geom_dim.y;
        //     v[2] += modal_geom_dim.y;
        //     break;
        //   case 21:
        //     v[0].x += modal_geom_dim.y;
        //     v[1].x += 2 * modal_geom_dim.y;
        //     v[1].y += modal_geom_dim.y;
        //     v[2].y += modal_geom_dim.y;
        //     break;
        //   case 22:
        //     v[1] += modal_geom_dim.x;
        //     v[2].y += 2 * modal_geom_dim.x;
        //     break;
        //   case 23:
        //     v[0].x += modal_geom_dim.x;
        //     v[1].x += modal_geom_dim.x;
        //     v[1].y += 2 * modal_geom_dim.x;
        //     v[2].y += modal_geom_dim.x;
        //     break;
        //   case 25:
        //     v[2].y = v[3].y = modal_geom_pos.y + modal_geom_dim.x;
        //     break;
        // }
        // if (info & 0x04) {
        //   oasis_read_repetition(in, factor, modal_repetition);
        //   polygon->repetition.copy_from(modal_repetition);
        // }
      } break;
      case OasisRecord::CIRCLE: {
        uint8_t info;
        oasis_read(&info, 1, 1, in);
        if (info & 0x01) {
          modal_layer = (uint32_t)oasis_read_unsigned_integer(in);
        }
        if (info & 0x02) {
          modal_datatype = (uint32_t)oasis_read_unsigned_integer(in);
        }
        if (info & 0x20) {
          modal_circle_radius = factor * oasis_read_unsigned_integer(in);
        }
        if (info & 0x10) {
          double x = factor * oasis_read_integer(in);
          if (modal_absolute_pos) {
            modal_geom_pos.x = x;
          } else {
            modal_geom_pos.x += x;
          }
        }
        if (info & 0x08) {
          double y = factor * oasis_read_integer(in);
          if (modal_absolute_pos) {
            modal_geom_pos.y = y;
          } else {
            modal_geom_pos.y += y;
          }
        }
        auto circle = Circle::create();
        circle->init(modal_geom_pos, modal_circle_radius, tolerance, modal_layer, modal_datatype);
        if (info & 0x04) {
          oasis_read_repetition(in, factor, modal_repetition);
          circle->repetition = modal_repetition;
        }
        next_property = &circle->properties;
        // cell->circles.push_back(circle);
        cell->spawn(circle);
      } break;
      case OasisRecord::PROPERTY:
      case OasisRecord::LAST_PROPERTY: {
        auto property = Property::create();
        next_property->push_back(property);

        uint8_t info;
        if (record == OasisRecord::LAST_PROPERTY) {
          info = 0x08;
        } else {
          oasis_read(&info, 1, 1, in);
        }
        if (info & 0x04) {
          // 明确名称
          if (info & 0x02) {
            // 参考编号
            property->reference_number = oasis_read_unsigned_integer(in);
            unfinished_property_name.push_back(property);
            modal_property_unfinished = true;
          } else {
            std::string str((char*)oasis_read_string(in, true, len));
            property->name = str;
            modal_property_unfinished = false;
          }
          modal_property = property;
        } else {
          // 使用模态变量
          if (modal_property_unfinished) {
            property->name = modal_property->name;
            unfinished_property_name.push_back(property);
          } else {
            property->name = modal_property->name;
          }
        }
        if (info & 0x08) {
          // 使用模态值列表
          property->values = modal_property_value_list;
          auto src = modal_property_value_list;
          auto dst = property->values;
          for (auto i = 0; i < src.size(); i++) {
            auto it = std::find(unfinished_property_value.begin(), unfinished_property_value.end(), src[i]);
            if (it != unfinished_property_value.end()) {
              unfinished_property_value.push_back(dst[i]);
            }
          }
        } else {
          // 明确值列表
          uint64_t num_values = info >> 4;
          if (num_values == 15) {
            num_values = oasis_read_unsigned_integer(in);
          }
          for (; num_values > 0; num_values--) {
            auto property_value = PropertyValue::create();
            property->values.push_back(property_value);

            OasisDataType data_type;
            oasis_read(&data_type, 1, 1, in);
            switch (data_type) {
              case OasisDataType::RealPositiveInteger:
              case OasisDataType::RealNegativeInteger:
              case OasisDataType::RealPositiveReciprocal:
              case OasisDataType::RealNegativeReciprocal:
              case OasisDataType::RealPositiveRatio:
              case OasisDataType::RealNegativeRatio:
              case OasisDataType::RealFloat:
              case OasisDataType::RealDouble: {
                property_value->type = PropertyType::Real;
                property_value->real = oasis_read_real_by_type(in, data_type);
              } break;
              case OasisDataType::UnsignedInteger: {
                property_value->type = PropertyType::UnsignedInteger;
                property_value->unsigned_integer = oasis_read_unsigned_integer(in);
              } break;
              case OasisDataType::SignedInteger: {
                property_value->type = PropertyType::Integer;
                property_value->integer = oasis_read_integer(in);
              } break;
              case OasisDataType::AString:
              case OasisDataType::BString:
              case OasisDataType::NString: {
                property_value->type = PropertyType::String;
                uint64_t count = 0;
                std::string str((char*)oasis_read_string(in, false, count));
                property_value->string = str;
              } break;
              case OasisDataType::ReferenceA:
              case OasisDataType::ReferenceB:
              case OasisDataType::ReferenceN: {
                property_value->type = PropertyType::ReferenceNumber;
                property_value->reference_number = oasis_read_unsigned_integer(in);
                unfinished_property_value.push_back(property_value);
              } break;
            }
          }
          modal_property_value_list = property->values;
        }
      } break;
      case OasisRecord::XNAME_IMPLICIT: {
        // oasis_read_unsigned_integer(in);
        // free_allocation(oasis_read_string(in, false, len));
        // if (error_logger)
        //   fputs("[GDSTK] Record type XNAME ignored.\n", error_logger);
        // if (error_code)
        //   *error_code = ErrorCode::UnsupportedRecord;
      } break;
      case OasisRecord::XNAME: {
        // oasis_read_unsigned_integer(in);
        // free_allocation(oasis_read_string(in, false, len));
        // oasis_read_unsigned_integer(in);
        // if (error_logger)
        //   fputs("[GDSTK] Record type XNAME ignored.\n", error_logger);
        // if (error_code)
        //   *error_code = ErrorCode::UnsupportedRecord;
      } break;
      case OasisRecord::XELEMENT: {
        // oasis_read_unsigned_integer(in);
        // free_allocation(oasis_read_string(in, false, len));
        // if (error_logger)
        //   fputs("[GDSTK] Record type XELEMENT ignored.\n", error_logger);
        // if (error_code)
        //   *error_code = ErrorCode::UnsupportedRecord;
      } break;
      case OasisRecord::XGEOMETRY: {
        // xgeometry-info-byte 包含的是一个bit模式： '000XYRDL'.
        uint8_t info;
        oasis_read(&info, 1, 1, in);
        // xgeometry-attribute
        auto attribute = (uint32_t)oasis_read_unsigned_integer(in);
        if (info & 0x01) {
          modal_layer = (uint32_t)oasis_read_unsigned_integer(in);
        }
        if (info & 0x02) {
          modal_datatype = (uint32_t)oasis_read_unsigned_integer(in);
        }
        // xgeometry-string
        std::string str((char*)oasis_read_string(in, false, len));
        std::string x_string = str.substr(0, len);

        if (info & 0x10) {
          double x = factor * oasis_read_integer(in);
          if (modal_absolute_pos) {
            modal_geom_pos.x = x;
          } else {
            modal_geom_pos.x += x;
          }
        }
        if (info & 0x08) {
          double y = factor * oasis_read_integer(in);
          if (modal_absolute_pos) {
            modal_geom_pos.y = y;
          } else {
            modal_geom_pos.y += y;
          }
        }
        if (info & 0x04) {
          oasis_read_repetition(in, factor, modal_repetition);
        }

        // if (attribute == uint8_t(OasisXElementType::Text)) {
        //   auto text = Text::create();
        //   text->origin = modal_geom_pos;
        //   text->layer = modal_layer;
        //   text->datatype = modal_datatype;
        //   // 解析json, 参考text_to_oas函数
        //   auto j = json::parse(x_string);
        //   text->rotation = j["a"] * factor;
        //   text->magnification = j["b"] * factor;
        //   text->anchor = qlib::Anchor(j["c"]);
        //   text->x_reflection = int(j["d"]);

        //   next_property = &text->properties;
        //   cell->spawn(text);
        // }

        // if (attribute == uint8_t(OasisXElementType::Ellipse)) {
        //   auto ellipse = Ellipse::create();
        //   ellipse->center = modal_geom_pos;
        //   ellipse->layer = modal_layer;
        //   ellipse->datatype = modal_datatype;
        //   // 解析json, 参考ellipse_to_oas函数
        //   auto j = json::parse(x_string);
        //   ellipse->rotation = j["a"] * factor;
        //   ellipse->radius = Vec2d{j["b"], j["c"]} * factor;

        //   next_property = &ellipse->properties;
        //   cell->spawn(ellipse);
        // }

        // if (attribute == uint8_t(OasisXElementType::Fan)) {
        //   auto fan = Fan::create();
        //   fan->center = modal_geom_pos;
        //   fan->layer = modal_layer;
        //   fan->datatype = modal_datatype;
        //   // 解析json, 参考fan_to_oas函数
        //   auto j = json::parse(x_string);
        //   fan->rotation = j["a"] * factor;
        //   fan->radius = j["b"] * factor;
        //   fan->angle = Vec2d{j["c"], j["d"]} * factor;

        //   next_property = &fan->properties;
        //   cell->spawn(fan);
        // }

        if (attribute == uint8_t(OasisXElementType::Ruler)) {
          auto ruler = Ruler::create();
          ruler->center = modal_geom_pos;
          ruler->layer = modal_layer;
          ruler->datatype = modal_datatype;
          // 解析json, 参考fan_to_oas函数
          auto j = json::parse(x_string);
          ruler->start = Vec2d{j["a"], j["b"]} * factor;
          ruler->end = Vec2d{j["c"], j["d"]} * factor;

          next_property = &ruler->properties;
          cell->spawn(ruler);
        }

        // if (error_logger)
        //   fputs("[GDSTK] Record type XGEOMETRY ignored.\n", error_logger);
        // if (error_code)
        //   *error_code = ErrorCode::UnsupportedRecord;
      } break;
      case OasisRecord::CBLOCK: {
        if (oasis_read_unsigned_integer(in) != 0) {
          if (error_logger)
            fputs("[GDSTK] CBLOCK compression method not supported.\n", error_logger);
          if (error_code)
            *error_code = ErrorCode::InvalidFile;
          oasis_read_unsigned_integer(in);
          len = oasis_read_unsigned_integer(in);
          assert(len <= INT64_MAX);
          FSEEK64(in.file, (int64_t)len, SEEK_SET);
        } else {
          z_stream s = {};
          s.zalloc = zalloc;
          s.zfree = zfree;
          in.data_size = oasis_read_unsigned_integer(in);
          s.avail_out = (uInt)in.data_size;
          s.avail_in = (uInt)oasis_read_unsigned_integer(in);
          in.data = (uint8_t*)allocate(in.data_size);
          in.cursor = in.data;
          s.next_out = in.data;
          uint8_t* data = (uint8_t*)allocate(s.avail_in);
          s.next_in = (Bytef*)data;
          if (fread(s.next_in, 1, s.avail_in, in.file) != s.avail_in) {
            if (error_logger)
              fputs("[GDSTK] Unable to read full CBLOCK.\n", error_logger);
            if (error_code)
              *error_code = ErrorCode::InvalidFile;
          }
          if (inflateInit2(&s, -15) != Z_OK) {
            if (error_logger)
              fputs("[GDSTK] Unable to initialize zlib.\n", error_logger);
            if (error_code)
              *error_code = ErrorCode::ZlibError;
          }
          int ret = inflate(&s, Z_FINISH);
          if (ret != Z_STREAM_END) {
            if (error_logger)
              fputs("[GDSTK] Unable to decompress CBLOCK.\n", error_logger);
            if (error_code)
              *error_code = ErrorCode::ZlibError;
          }
          free_allocation(data);
          inflateEnd(&s);
          // Empty CBLOCK
          if (in.data_size == 0) {
            free_allocation(in.data);
            in.data = NULL;
          }
        }
      } break;
      default:
        // if (error_logger)
        //   fprintf(error_logger, "[GDSTK] Unknown record type <0x%02X>.\n", (uint8_t)record);
        if (error_code)
          *error_code = ErrorCode::UnsupportedRecord;
    }
  }
  if (in.error_code != ErrorCode::NoError && error_code)
    *error_code = in.error_code;

CLEANUP:
  fclose(in.file);
  library->setReferenceInstanceName();
  return library;
}

static uint64_t max_string_length(const PropertyArray& properties) {
  uint64_t result = 0;
  for (auto& it_p : properties) {
    uint64_t len = it_p->name.length();
    if (len > result) {
      result = len;
    }
    for (auto& it_v : it_p->values) {
      len = it_p->name.length();
      if (len > result) {
        result = len;
      }
    }
  }
  return result;
}

ErrorCode DB::properties_to_oas(PropertyArray& properties, OasisStream& out, OasisState& state) {
  ErrorCode error_code = ErrorCode::NoError;

  for (auto& prop : properties) {
    uint8_t info = 0x06;
    if (is_gds_property(prop))
      info |= 0x01;

    uint64_t value_count = prop->values.size();
    if (value_count > 14) {
      info |= 0xF0;
    } else {
      info |= (uint8_t)(value_count & 0x0F) << 4;
    }

    oasis_putc((int)OasisRecord::PROPERTY, out);
    oasis_putc(info, out);

    // 只存属性名称id;
    // auto p_it = state.property_name_map.find(prop->name);
    uint64_t index = 0;
    auto& name_map = state.property_name_map;
    if (name_map.find(prop->name) == name_map.end()) {
      index = name_map.size();
      name_map[prop->name] = index;
    } else {
      index = name_map[prop->name];
    }

    // if (state.property_name_map.find(prop->name) == state.property_name_map.end()) {
    //   index = state.property_name_map.size();
    //   state.property_name_map[prop->name] = index;
    // } else {
    //   index = state.property_name_map[prop->name];
    // }
    oasis_write_unsigned_integer(out, index);

    if (value_count > 14) {
      oasis_write_unsigned_integer(out, value_count);
    }

    for (auto& value : prop->values) {
      switch (value->type) {
        case PropertyType::Real:
          oasis_write_real(out, value->real);
          break;
        case PropertyType::UnsignedInteger:
          oasis_putc(8, out);
          oasis_write_unsigned_integer(out, value->unsigned_integer);
          break;
        case PropertyType::Integer:
          oasis_putc(9, out);
          oasis_write_integer(out, value->integer);
          break;
        case PropertyType::String: {
          bool space = false;
          bool binary = false;
          for (auto& c : value->string) {
            uint8_t b = static_cast<uint8_t>(c);
            if (b < 0x20 || b > 0x7E) {
              binary = true;
              break;
            } else if (b == 0x20) {
              space = true;
            }
          }
          if (binary) {
            oasis_putc(14, out);
          } else if (space) {
            oasis_putc(13, out);
          } else {
            oasis_putc(15, out);
          }
          // 只存属性-value名称id
          // auto pv_it = state.property_value_name_map.find(value->string);
          // if (pv_it == state.property_value_name_map.end()) {
          //   pv_it = state.property_value_name_map.insert(std::make_pair(value->string, state.property_value_name_id++)).first;
          // }
          // oasis_write_unsigned_integer(out, pv_it->second);
          auto& name_map = state.property_value_name_map;
          if (name_map.find(value->string) == name_map.end()) {
            index = name_map.size();
            name_map[value->string] = index;
          } else {
            index = name_map[value->string];
          }
          oasis_write_unsigned_integer(out, index);
        }
      }
    }
  }
  return error_code;
}

ErrorCode DB::rectangle_to_oas(Rectangle::s_ptr rectangle, OasisStream& out, OasisState& state) {
  // 缩放系数
  ErrorCode error_code = ErrorCode::NoError;
  bool has_repetition = rectangle->repetition.get_count() > 1;

  // Vec2i pos = Vec2i{int64_t(std::round(state.scaling * rectangle->pos.x)), int64_t(std::round(state.scaling * rectangle->pos.y))};
  int64_t w = int64_t(std::round(state.scaling * rectangle->width));
  int64_t h = int64_t(std::round(state.scaling * rectangle->height));

  bool is_square = w == h;
  uint8_t info;
  if (is_square) {
    info = 0xDB;
  } else {
    info = 0x7B;
  }
  if (has_repetition)
    info |= 0x04;
  oasis_putc((int)OasisRecord::RECTANGLE, out);
  oasis_putc(info, out);
  oasis_write_unsigned_integer(out, rectangle->layer);
  oasis_write_unsigned_integer(out, rectangle->datatype);
  oasis_write_unsigned_integer(out, w);
  if (!is_square)
    oasis_write_unsigned_integer(out, h);
  oasis_write_integer(out, int64_t(std::round(state.scaling * rectangle->pos.x)));
  oasis_write_integer(out, int64_t(std::round(state.scaling * rectangle->pos.y)));
  // if (is_square)
  //     printf("SQUARE @ (%ld, %ld) w %ld\n", corner.x, corner.y, size.x);
  // else
  //     printf("RECTANGLE @ (%ld, %ld) w %ld, h %ld\n", corner.x, corner.y, size.x,
  //     size.y);
  // }
  if (has_repetition)
    oasis_write_repetition(out, rectangle->repetition, state.scaling);

  ErrorCode err = DB::properties_to_oas(rectangle->properties, out, state);
  if (err != ErrorCode::NoError)
    error_code = err;
  return error_code;
}

ErrorCode DB::circle_to_oas(Circle::s_ptr circle, OasisStream& out, OasisState& state) {
  ErrorCode error_code = ErrorCode::NoError;
  bool has_repetition = circle->repetition.get_count() > 1;

  uint8_t info = 0x3B;
  if (has_repetition)
    info |= 0x04;
  oasis_putc((int)OasisRecord::CIRCLE, out);
  oasis_putc(info, out);
  oasis_write_unsigned_integer(out, circle->layer);
  oasis_write_unsigned_integer(out, circle->datatype);
  oasis_write_unsigned_integer(out, (uint64_t)llround(circle->radius * state.scaling));
  oasis_write_integer(out, (int64_t)llround(circle->center.x * state.scaling));
  oasis_write_integer(out, (int64_t)llround(circle->center.y * state.scaling));
  // printf("CIRCLE @ (%lf, %lf) r %lf\n", center.x, center.y, radius);
  if (has_repetition)
    oasis_write_repetition(out, circle->repetition, state.scaling);
  ErrorCode err = DB::properties_to_oas(circle->properties, out, state);
  if (err != ErrorCode::NoError)
    error_code = err;
  return error_code;
}

// 参考圆形
ErrorCode DB::ellipse_to_oas(Ellipse::s_ptr circle, OasisStream& out, OasisState& state) {
  ErrorCode error_code = ErrorCode::NoError;
  bool has_repetition = circle->repetition.get_count() > 1;

  uint8_t info = 0x3B;
  if (has_repetition)
    info |= 0x04;
  oasis_putc((int)OasisRecord::CIRCLE, out);
  oasis_putc(info, out);
  oasis_write_unsigned_integer(out, circle->layer);
  oasis_write_unsigned_integer(out, circle->datatype);
  oasis_write_unsigned_integer(out, (uint64_t)llround(circle->radius.x * state.scaling));
  oasis_write_integer(out, (int64_t)llround(circle->center.x * state.scaling));
  oasis_write_integer(out, (int64_t)llround(circle->center.y * state.scaling));
  // printf("CIRCLE @ (%lf, %lf) r %lf\n", center.x, center.y, radius);
  if (has_repetition)
    oasis_write_repetition(out, circle->repetition, state.scaling);
  ErrorCode err = DB::properties_to_oas(circle->properties, out, state);
  if (err != ErrorCode::NoError)
    error_code = err;
  return error_code;
}

// 参考圆形
ErrorCode DB::fan_to_oas(Fan::s_ptr circle, OasisStream& out, OasisState& state) {
  ErrorCode error_code = ErrorCode::NoError;
  bool has_repetition = circle->repetition.get_count() > 1;

  uint8_t info = 0x3B;
  if (has_repetition)
    info |= 0x04;
  oasis_putc((int)OasisRecord::CIRCLE, out);
  oasis_putc(info, out);
  oasis_write_unsigned_integer(out, circle->layer);
  oasis_write_unsigned_integer(out, circle->datatype);
  oasis_write_unsigned_integer(out, (uint64_t)llround(circle->radius * state.scaling));
  oasis_write_integer(out, (int64_t)llround(circle->center.x * state.scaling));
  oasis_write_integer(out, (int64_t)llround(circle->center.y * state.scaling));
  // printf("CIRCLE @ (%lf, %lf) r %lf\n", center.x, center.y, radius);
  if (has_repetition)
    oasis_write_repetition(out, circle->repetition, state.scaling);
  ErrorCode err = DB::properties_to_oas(circle->properties, out, state);
  if (err != ErrorCode::NoError)
    error_code = err;
  return error_code;
}

ErrorCode DB::polygon_to_oas(Polygon::s_ptr polygon, OasisStream& out, OasisState& state) {
  ErrorCode error_code = ErrorCode::NoError;

  bool has_repetition = polygon->repetition.get_count() > 1;
  Vec2iArray points = {};
  scale_and_round_array(polygon->points, state.scaling, points);

  {
    uint8_t info = 0x3B;
    if (has_repetition)
      info |= 0x04;
    oasis_putc((int)OasisRecord::POLYGON, out);
    oasis_putc(info, out);
    oasis_write_unsigned_integer(out, polygon->layer);
    oasis_write_unsigned_integer(out, polygon->datatype);
    // oasis_write_point_list(out, points, true);
    oasis_write_point_list(out, points, true);
    oasis_write_integer(out, points[0].x);
    oasis_write_integer(out, points[0].y);
    // printf("POLYGON @ (%ld, %ld)\n", points[0].x, points[0].y);
  }
  if (has_repetition)
    oasis_write_repetition(out, polygon->repetition, state.scaling);
  ErrorCode err = DB::properties_to_oas(polygon->properties, out, state);
  if (err != ErrorCode::NoError)
    error_code = err;

  points.clear();
  return error_code;
}

ErrorCode DB::path_to_oas(Path::s_ptr path, OasisStream& out, OasisState& state) {
  ErrorCode error_code = ErrorCode::NoError;
  bool has_repetition = path->repetition.get_count() > 1;

  {
    uint8_t info = 0xfb;
    if (has_repetition)
      info |= 0x04;
    oasis_putc((int)OasisRecord::PATH, out);
    oasis_putc(info, out);
    oasis_write_unsigned_integer(out, path->layer);
    oasis_write_unsigned_integer(out, path->datatype);
    uint64_t half_width = (uint64_t)llround(path->width * state.scaling);
    oasis_write_unsigned_integer(out, half_width);

    switch (path->end_type) {
      case EndType::Extended: {
        uint8_t extension_scheme = 0;
        int64_t start_extension = (int64_t)llround(path->end_extensions.u * state.scaling);
        int64_t end_extension = (int64_t)llround(path->end_extensions.v * state.scaling);
        if (start_extension == 0) {
          extension_scheme |= 0x04;
        } else if (start_extension > 0 && (uint64_t)start_extension == half_width) {
          extension_scheme |= 0x08;
          start_extension = 0;
        } else {
          extension_scheme |= 0x0C;
        }
        if (end_extension == 0) {
          extension_scheme |= 0x01;
        } else if (end_extension > 0 && (uint64_t)end_extension == half_width) {
          extension_scheme |= 0x02;
          end_extension = 0;
        } else {
          extension_scheme |= 0x03;
        }
        oasis_putc(extension_scheme, out);
        if (start_extension != 0)
          oasis_write_integer(out, start_extension);
        if (end_extension != 0)
          oasis_write_integer(out, end_extension);
      } break;
      case EndType::HalfWidth:
        oasis_putc(0x0A, out);
        break;
      default:  // Flush
        oasis_putc(0x05, out);
    }

    auto& points = path->spine.points;
    oasis_write_point_list(out, points, double(state.scaling), true);
    oasis_write_integer(out, (int64_t)llround(points[0].x * state.scaling));
    oasis_write_integer(out, (int64_t)llround(points[0].y * state.scaling));
  }
  if (has_repetition)
    oasis_write_repetition(out, path->repetition, state.scaling);

  ErrorCode err = DB::properties_to_oas(path->properties, out, state);
  if (err != ErrorCode::NoError)
    error_code = err;

  return error_code;
}

ErrorCode DB::label_to_oas(Label::s_ptr label, std::unordered_map<std::string, uint64_t>& text_string_map, OasisStream& out, OasisState& state) {
  ErrorCode error_code = ErrorCode::NoError;
  uint8_t info = 0x7B;
  bool has_repetition = label->repetition.get_count() > 1;
  if (has_repetition)
    info |= 0x04;
  oasis_putc((int)OasisRecord::TEXT, out);
  oasis_putc(info, out);
  uint64_t index;
  if (text_string_map.find(label->text) != text_string_map.end()) {
    index = text_string_map[label->text];
  } else {
    index = text_string_map.size();
    text_string_map[label->text] = index;
  }
  oasis_write_unsigned_integer(out, index);
  oasis_write_unsigned_integer(out, label->layer);
  oasis_write_unsigned_integer(out, label->datatype);
  oasis_write_integer(out, (int64_t)llround(label->origin.x * state.scaling));
  oasis_write_integer(out, (int64_t)llround(label->origin.y * state.scaling));
  if (has_repetition)
    oasis_write_repetition(out, label->repetition, state.scaling);
  ErrorCode err = DB::properties_to_oas(label->properties, out, state);
  if (err != ErrorCode::NoError)
    error_code = err;

  return error_code;
}

ErrorCode DB::text_to_oas(Text::s_ptr label, std::unordered_map<std::string, uint64_t>& text_string_map, OasisStream& out, OasisState& state) {
  ErrorCode error_code = ErrorCode::NoError;
  uint8_t info = 0x7B;
  bool has_repetition = label->repetition.get_count() > 1;
  if (has_repetition)
    info |= 0x04;
  oasis_putc((int)OasisRecord::TEXT, out);
  oasis_putc(info, out);
  uint64_t index;
  if (text_string_map.find(label->text) != text_string_map.end()) {
    index = text_string_map[label->text];
  } else {
    index = text_string_map.size();
    text_string_map[label->text] = index;
  }
  oasis_write_unsigned_integer(out, index);
  oasis_write_unsigned_integer(out, label->layer);
  oasis_write_unsigned_integer(out, label->datatype);
  oasis_write_integer(out, (int64_t)llround(label->origin.x * state.scaling));
  oasis_write_integer(out, (int64_t)llround(label->origin.y * state.scaling));
  if (has_repetition)
    oasis_write_repetition(out, label->repetition, state.scaling);
  ErrorCode err = DB::properties_to_oas(label->properties, out, state);
  if (err != ErrorCode::NoError)
    error_code = err;

  return error_code;
}

ErrorCode DB::pin_to_oas(Pin::s_ptr label, std::unordered_map<std::string, uint64_t>& text_string_map, OasisStream& out, OasisState& state) {
  ErrorCode error_code = ErrorCode::NoError;
  uint8_t info = 0x7B;
  bool has_repetition = label->repetition.get_count() > 1;
  if (has_repetition)
    info |= 0x04;
  oasis_putc((int)OasisRecord::TEXT, out);
  oasis_putc(info, out);
  uint64_t index;
  if (text_string_map.find(label->text) != text_string_map.end()) {
    index = text_string_map[label->text];
  } else {
    index = text_string_map.size();
    text_string_map[label->text] = index;
  }
  oasis_write_unsigned_integer(out, index);
  oasis_write_unsigned_integer(out, label->layer);
  oasis_write_unsigned_integer(out, label->datatype);
  oasis_write_integer(out, (int64_t)llround(label->origin.x * state.scaling));
  oasis_write_integer(out, (int64_t)llround(label->origin.y * state.scaling));
  if (has_repetition)
    oasis_write_repetition(out, label->repetition, state.scaling);
  ErrorCode err = DB::properties_to_oas(label->properties, out, state);
  if (err != ErrorCode::NoError)
    error_code = err;

  return error_code;
}

ErrorCode DB::keypoint_to_oas(KeyPoint::s_ptr label, std::unordered_map<std::string, uint64_t>& text_string_map, OasisStream& out,
                              OasisState& state) {
  ErrorCode error_code = ErrorCode::NoError;
  uint8_t info = 0x7B;
  bool has_repetition = label->repetition.get_count() > 1;
  if (has_repetition)
    info |= 0x04;
  oasis_putc((int)OasisRecord::TEXT, out);
  oasis_putc(info, out);
  uint64_t index;
  if (text_string_map.find(label->text) != text_string_map.end()) {
    index = text_string_map[label->text];
  } else {
    index = text_string_map.size();
    text_string_map[label->text] = index;
  }
  oasis_write_unsigned_integer(out, index);
  oasis_write_unsigned_integer(out, label->layer);
  oasis_write_unsigned_integer(out, label->datatype);
  oasis_write_integer(out, (int64_t)llround(label->origin.x * state.scaling));
  oasis_write_integer(out, (int64_t)llround(label->origin.y * state.scaling));
  if (has_repetition)
    oasis_write_repetition(out, label->repetition, state.scaling);
  ErrorCode err = DB::properties_to_oas(label->properties, out, state);
  if (err != ErrorCode::NoError)
    error_code = err;

  return error_code;
}

ErrorCode DB::reference_to_oas(Reference::s_ptr ref, std::unordered_map<std::string, uint64_t>& cell_name_map, OasisStream& out, OasisState& state) {
  ErrorCode error_code = ErrorCode::NoError;
  if (ref->type == ReferenceType::RawCell) {
    if (error_logger)
      fputs("[GDSTK] Reference to a RawCell cannot be used in an OASIS file.\n", error_logger);
    error_code = ErrorCode::MissingReference;
    // continue;
  }
  auto name_ = (ref->type == ReferenceType::Cell) ? ref->cell->name : ref->name;
  bool reference_exists = cell_name_map.find(name_) != cell_name_map.end();
  uint8_t info = reference_exists ? 0xF0 : 0xB0;
  bool has_repetition = ref->repetition.get_count() > 1;
  if (has_repetition)
    info |= 0x08;
  if (ref->x_reflection)
    info |= 0x01;
  int64_t m;
  if (ref->magnification == 1.0 && is_multiple_of_pi_over_2(ref->rotation, m)) {
    if (m < 0) {
      info |= ((uint8_t)(0x03 & ((m % 4) + 4))) << 1;
    } else {
      info |= ((uint8_t)(0x03 & (m % 4))) << 1;
    }
    oasis_putc((int)OasisRecord::PLACEMENT, out);
    oasis_putc(info, out);
    if (reference_exists) {
      uint64_t index = cell_name_map[name_];
      oasis_write_unsigned_integer(out, index);
    } else {
      uint64_t len = name_.length();
      oasis_write_unsigned_integer(out, len);
      oasis_write(ref->name.c_str(), 1, len, out);
    }
  } else {
    if (ref->magnification != 1)
      info |= 0x04;
    if (ref->rotation != 0)
      info |= 0x02;
    oasis_putc((int)OasisRecord::PLACEMENT_TRANSFORM, out);
    oasis_putc(info, out);
    if (reference_exists) {
      uint64_t index = cell_name_map[name_];
      oasis_write_unsigned_integer(out, index);
    } else {
      uint64_t len = name_.length();
      oasis_write_unsigned_integer(out, len);
      oasis_write(ref->name.c_str(), 1, len, out);
    }
    if (ref->magnification != 1) {
      oasis_write_real(out, ref->magnification);
    }
    if (ref->rotation != 0) {
      oasis_write_real(out, ref->rotation * (180.0 / M_PI));
    }
  }
  oasis_write_integer(out, (int64_t)llround(ref->origin.x * state.scaling));
  oasis_write_integer(out, (int64_t)llround(ref->origin.y * state.scaling));
  if (has_repetition)
    oasis_write_repetition(out, ref->repetition, state.scaling);
  // 这里写属性很慢，之前引用添加太多属性了
  ErrorCode err = DB::properties_to_oas(ref->properties, out, state);
  if (err != ErrorCode::NoError)
    error_code = err;
  return error_code;
}

// ErrorCode DB::text_to_oas(Text::s_ptr text, OasisStream& out, OasisState& state) {
//   ErrorCode error_code = ErrorCode::NoError;
//   bool has_repetition = text->repetition.get_count() > 1;

//   uint8_t info = 0x1B;
//   if (has_repetition)
//     info |= 0x04;
//   oasis_putc((int)OasisRecord::XGEOMETRY, out);
//   oasis_putc(info, out);

//   // <xgeometry-attribute>
//   oasis_write_unsigned_integer(out, (int)OasisXElementType::Text);
//   // <l-d>
//   oasis_write_unsigned_integer(out, text->layer);
//   oasis_write_unsigned_integer(out, text->datatype);

//   // obj -> json str
//   json j;
//   j["a"] = (int64_t)llround(text->rotation * state.scaling);
//   j["b"] = (int64_t)llround(text->magnification * state.scaling);
//   j["c"] = int(text->anchor);
//   j["d"] = int(text->x_reflection);
//   std::string str = j.dump();

//   // <xgeometry-string>
//   uint64_t len = str.length();
//   oasis_write_unsigned_integer(out, len);
//   oasis_write(str.c_str(), 1, len, out);

//   // <x> <y>
//   oasis_write_integer(out, (int64_t)llround(text->origin.x * state.scaling));
//   oasis_write_integer(out, (int64_t)llround(text->origin.y * state.scaling));

//   if (has_repetition)
//     oasis_write_repetition(out, text->repetition, state.scaling);
//   ErrorCode err = DB::properties_to_oas(text->properties, out, state);
//   if (err != ErrorCode::NoError)
//     error_code = err;
//   return error_code;
// }

// ErrorCode DB::ellipse_to_oas(Ellipse::s_ptr ellipse, OasisStream& out, OasisState& state) {
//   ErrorCode error_code = ErrorCode::NoError;
//   bool has_repetition = ellipse->repetition.get_count() > 1;

//   uint8_t info = 0x1B;
//   if (has_repetition)
//     info |= 0x04;
//   oasis_putc((int)OasisRecord::XGEOMETRY, out);
//   oasis_putc(info, out);
//   oasis_write_unsigned_integer(out, (int)OasisXElementType::Ellipse);
//   oasis_write_unsigned_integer(out, ellipse->layer);
//   oasis_write_unsigned_integer(out, ellipse->datatype);

//   // 转换对象为json字符串进行存储
//   json j;
//   j["a"] = (int64_t)llround(ellipse->rotation * state.scaling);
//   j["b"] = (int64_t)llround(ellipse->radius.x * state.scaling);
//   j["c"] = (int64_t)llround(ellipse->radius.y * state.scaling);
//   std::string str = j.dump();
//   // std::vector<std::uint8_t> v_bson = json::to_bson(j);

//   uint64_t len = str.length();
//   oasis_write_unsigned_integer(out, len);
//   oasis_write(str.c_str(), 1, len, out);
//   // oasis_write(&v_bson, 1, len, out);

//   oasis_write_integer(out, (int64_t)llround(ellipse->center.x * state.scaling));
//   oasis_write_integer(out, (int64_t)llround(ellipse->center.y * state.scaling));
//   // printf("CIRCLE @ (%lf, %lf) r %lf\n", center.x, center.y, radius);
//   if (has_repetition)
//     oasis_write_repetition(out, ellipse->repetition, state.scaling);
//   ErrorCode err = DB::properties_to_oas(ellipse->properties, out, state);
//   if (err != ErrorCode::NoError)
//     error_code = err;
//   return error_code;
// }

// ErrorCode DB::fan_to_oas(Fan::s_ptr fan, OasisStream& out, OasisState& state) {
//   ErrorCode error_code = ErrorCode::NoError;
//   bool has_repetition = fan->repetition.get_count() > 1;

//   uint8_t info = 0x1B;
//   if (has_repetition)
//     info |= 0x04;
//   oasis_putc((int)OasisRecord::XGEOMETRY, out);
//   oasis_putc(info, out);
//   oasis_write_unsigned_integer(out, (int)OasisXElementType::Fan);
//   oasis_write_unsigned_integer(out, fan->layer);
//   oasis_write_unsigned_integer(out, fan->datatype);

//   // 转换对象为json字符串进行存储
//   json j;
//   j["a"] = (int64_t)llround(fan->rotation * state.scaling);
//   j["b"] = (int64_t)llround(fan->radius * state.scaling);
//   j["c"] = (int64_t)llround(fan->angle.x * state.scaling);
//   j["d"] = (int64_t)llround(fan->angle.y * state.scaling);
//   std::string str = j.dump();
//   // std::vector<std::uint8_t> v_bson = json::to_bson(j);

//   uint64_t len = str.length();
//   oasis_write_unsigned_integer(out, len);
//   oasis_write(str.c_str(), 1, len, out);
//   // oasis_write(&v_bson, 1, len, out);

//   oasis_write_integer(out, (int64_t)llround(fan->center.x * state.scaling));
//   oasis_write_integer(out, (int64_t)llround(fan->center.y * state.scaling));

//   //
//   if (has_repetition)
//     oasis_write_repetition(out, fan->repetition, state.scaling);
//   ErrorCode err = DB::properties_to_oas(fan->properties, out, state);
//   if (err != ErrorCode::NoError)
//     error_code = err;
//   return error_code;
// }

ErrorCode DB::ruler_to_oas(Ruler::s_ptr ruler, OasisStream& out, OasisState& state) {
  ErrorCode error_code = ErrorCode::NoError;
  bool has_repetition = ruler->repetition.get_count() > 1;

  uint8_t info = 0x1B;
  if (has_repetition)
    info |= 0x04;
  oasis_putc((int)OasisRecord::XGEOMETRY, out);
  oasis_putc(info, out);
  oasis_write_unsigned_integer(out, (int)OasisXElementType::Ruler);
  oasis_write_unsigned_integer(out, ruler->layer);
  oasis_write_unsigned_integer(out, ruler->datatype);

  // 转换对象为json字符串进行存储
  json j;
  j["a"] = (int64_t)llround(ruler->start.x * state.scaling);
  j["b"] = (int64_t)llround(ruler->start.y * state.scaling);
  j["c"] = (int64_t)llround(ruler->end.x * state.scaling);
  j["d"] = (int64_t)llround(ruler->end.y * state.scaling);
  std::string str = j.dump();
  // std::vector<std::uint8_t> v_bson = json::to_bson(j);

  uint64_t len = str.length();
  oasis_write_unsigned_integer(out, len);
  oasis_write(str.c_str(), 1, len, out);
  // oasis_write(&v_bson, 1, len, out);

  oasis_write_integer(out, (int64_t)llround(ruler->center.x * state.scaling));
  oasis_write_integer(out, (int64_t)llround(ruler->center.y * state.scaling));

  //
  if (has_repetition)
    oasis_write_repetition(out, ruler->repetition, state.scaling);
  ErrorCode err = DB::properties_to_oas(ruler->properties, out, state);
  if (err != ErrorCode::NoError)
    error_code = err;
  return error_code;
}

ErrorCode DB::write_oas(Library::s_ptr library, const std::string& filename, uint8_t compression_level, bool detect_rectangles,
                        bool detect_trapezoids, double circle_tolerance, double standard_properties, OasisValidationType validation) {
  // 更新cell为map存储
  library->unit = 1e-6;
  library->precision = 1e-9;
  library->update_cell_map();
  auto& cell_map = library->cell_map;

  for (auto& [name, cell] : cell_map) {
    set_property(cell->properties, "type", uint64_t(cell->m_cell_type), false);
    set_property(cell->properties, "pcell_code", cell->m_pcell_code, false);
    for (auto& path : cell->m_dimension_diagrams) {
      set_property(cell->properties, "pcell_svg_paths", path, true);
    }
    for (auto [e, rect] : cell->reg.view<Rectangle::s_ptr>().each()) {
      set_property(rect->properties, "rotation", rect->rotation, false);
    }

    for (auto [e, circle] : cell->reg.view<Circle::s_ptr>().each()) {
      set_property(circle->properties, "type", uint64_t(OasisCircleType::Circle), false);
    }
    for (auto [e, fan] : cell->reg.view<Fan::s_ptr>().each()) {
      set_property(fan->properties, "type", uint64_t(OasisCircleType::Fan), false);
      set_property(fan->properties, "rotation", fan->rotation, false);
      set_property(fan->properties, "angle_start", fan->angle.x, false);
      set_property(fan->properties, "angle_end", fan->angle.y, false);
    }
    for (auto [e, ellipse] : cell->reg.view<Ellipse::s_ptr>().each()) {
      set_property(ellipse->properties, "type", uint64_t(OasisCircleType::Ellipse), false);
      set_property(ellipse->properties, "rotation", ellipse->rotation, false);
      set_property(ellipse->properties, "radius_y", ellipse->radius.y, false);
    }
    for (auto [e, path] : cell->reg.view<Path::s_ptr>().each()) {
      set_property(path->properties, "type", uint64_t(path->type), false);
      set_property(path->properties, "center_width", path->center_width, false);
      set_property(path->properties, "radius", path->radius, false);
    }
    for (auto [e, label] : cell->reg.view<Label::s_ptr>().each()) {

      // 适配自动布线需要用到的gds属性 存到oas中
      PropertyValueArray* values_7 = get_gds_property(label->properties, 7);
      PropertyValueArray* values_gds_7 = get_property(label->properties, "gds_7");
      std::string value_7;
      if (values_7 != nullptr) {
        if (values_7->size() == 2) {
          value_7 = (*values_7)[1]->string;
        }
      } else {
        if (values_gds_7 != nullptr) {
          if (values_gds_7->size() == 1) {
            value_7 = (*values_gds_7)[0]->string;
          }
        }
      }

      label->properties.clear();
      set_property(label->properties, "type", uint64_t(OasisTextType::Label), false);
      set_property(label->properties, "size", label->fontSize, false);
      set_property(label->properties, "magnification", label->magnification, false);
      set_property(label->properties, "anchor", uint64_t(label->anchor), false);
      set_property(label->properties, "rotation", label->rotation, false);
      set_property(label->properties, "x_reflection", uint64_t(label->x_reflection), false);

      if (value_7 != "") {
        set_property(label->properties, "gds_7", value_7, false);
        set_gds_property(label->properties, 7, value_7);
      }
    }
    for (auto [e, text] : cell->reg.view<Text::s_ptr>().each()) {
      set_property(text->properties, "type", uint64_t(OasisTextType::Text), false);
      set_property(text->properties, "size", text->fontSize, false);
      set_property(text->properties, "magnification", text->magnification, false);
      set_property(text->properties, "anchor", uint64_t(text->anchor), false);
      set_property(text->properties, "rotation", text->rotation, false);
      set_property(text->properties, "x_reflection", uint64_t(text->x_reflection), false);
    }
    for (auto [e, pin] : cell->reg.view<Pin::s_ptr>().each()) {
      set_property(pin->properties, "type", uint64_t(OasisTextType::Pin), false);
      set_property(pin->properties, "size", pin->fontSize, false);
      set_property(pin->properties, "magnification", pin->magnification, false);
      set_property(pin->properties, "anchor", uint64_t(pin->anchor), false);
      set_property(pin->properties, "rotation", pin->rotation, false);
      set_property(pin->properties, "x_reflection", uint64_t(pin->x_reflection), false);
    }
    for (auto [e, key] : cell->reg.view<KeyPoint::s_ptr>().each()) {
      set_property(key->properties, "type", uint64_t(OasisTextType::KeyPoint), false);
      set_property(key->properties, "size", key->fontSize, false);
      set_property(key->properties, "magnification", key->magnification, false);
      set_property(key->properties, "anchor", uint64_t(key->anchor), false);
      set_property(key->properties, "rotation", key->rotation, false);
      set_property(key->properties, "x_reflection", uint64_t(key->x_reflection), false);
      set_property(key->properties, "haveLabel", uint64_t(key->haveLabel), false);
    }
  }

  auto& properties = library->properties;
  auto unit = library->unit;
  auto precision = library->precision;

  uint8_t config_flags = 0;
  if (detect_rectangles)
    config_flags |= OASIS_CONFIG_DETECT_RECTANGLES;
  if (detect_trapezoids)
    config_flags |= OASIS_CONFIG_DETECT_TRAPEZOIDS;
  if (standard_properties)
    config_flags |= OASIS_CONFIG_STANDARD_PROPERTIES;
  if (validation == OasisValidationType::CRC32)
    config_flags |= OASIS_CONFIG_INCLUDE_CRC32;

  if (validation == OasisValidationType::CHECKSUM32)
    config_flags |= OASIS_CONFIG_INCLUDE_CHECKSUM32;

  ErrorCode error_code = ErrorCode::NoError;
  const uint64_t c_size = cell_map.size();
  OasisState state = {};
  state.property_value_name_id = 0;
  state.property_name_id = 0;
  state.circle_tolerance = circle_tolerance;
  state.config_flags = config_flags;

  if (compression_level > 9)
    compression_level = 9;

  OasisStream out;
  out.file = fopen(filename.c_str(), "wb");
  if (out.file == NULL) {
    if (error_logger)
      fputs("[GDSTK] Unable to open OASIS file for output.\n", error_logger);
    return ErrorCode::OutputFileOpenError;
  }
  out.data_size = 1024 * 1024;
  out.data = (uint8_t*)allocate(out.data_size);
  out.cursor = NULL;
  out.crc32 = state.config_flags & OASIS_CONFIG_INCLUDE_CRC32;
  out.checksum32 = state.config_flags & OASIS_CONFIG_INCLUDE_CHECKSUM32;
  if (out.crc32) {
    out.signature = crc32(0, NULL, 0);
  } else if (out.checksum32) {
    out.signature = 0;
  }
  out.error_code = ErrorCode::NoError;

  char header[] = {'%', 'S', 'E', 'M', 'I', '-', 'O', 'A', 'S', 'I', 'S', '\r', '\n', (char)OasisRecord::START, 3, '1', '.', '0'};
  oasis_write(header, 1, COUNT(header), out);

  state.scaling = unit / precision;
  oasis_write_real(out, 1e-6 / precision);
  oasis_putc(1, out);  // flag indicating that table-offsets will be stored in the END record

  if (state.config_flags & OASIS_CONFIG_PROPERTY_TOP_LEVEL) {
    remove_property(properties, s_top_level_property_name, true);
    CellPtrArray top_cells = {};
    // Array<RawCell*> top_rawcells = {};
    // top_level(top_cells, top_rawcells);
    for (uint64_t i = 0; i < top_cells.size(); i++) {
      set_property(properties, s_top_level_property_name, top_cells[i]->name, true);
    }
    top_cells.clear();
    // top_rawcells.clear();
  }

  if (state.config_flags & OASIS_CONFIG_PROPERTY_BOUNDING_BOX) {
    remove_property(properties, s_bounding_box_available_property_name, true);
    set_property(properties, s_bounding_box_available_property_name, (uint64_t)2, true);
  }

  if (state.config_flags & OASIS_CONFIG_PROPERTY_MAX_COUNTS) {
    uint64_t string_max = strlen(s_max_uint_size_property_name);
    uint64_t polygon_max = 0;
    uint64_t path_max = 0;

    uint64_t len = max_string_length(properties);
    if (len > string_max)
      string_max = len;

    Vec2dArray tmp_array = {};
    // for (auto& cell : cell_array) {
    for (auto& [name, cell] : cell_map) {
      len = cell->name.length();
      if (len > string_max)
        string_max = len;
      len = max_string_length(cell->properties);
      if (len > string_max)
        string_max = len;

      for (auto [e, poly] : cell->reg.view<Polygon::s_ptr>().each()) {
        len = max_string_length(poly->properties);
        if (len > string_max)
          string_max = len;
        len = poly->points.size();
        if (len > polygon_max)
          polygon_max = len;
      }

      for (auto [e, path] : cell->reg.view<Path::s_ptr>().each()) {
        len = max_string_length(path->properties);
        if (len > string_max)
          string_max = len;
        if (path->simple_path) {
          if (path->spine.points.size() > 1) {
            tmp_array.resize(0);
            // auto el = path->elements.data();
            // for (uint64_t ne = 0; ne < path->elements.size(); ne++, el++) {
            //   ErrorCode err = path->element_center(el, tmp_array);
            //   if (err != ErrorCode::NoError)
            //     error_code = err;
            //   len = tmp_array.size();
            //   if (len > path_max)
            //     path_max = len;
            // }
          }
        } else {
          PolygonPtrArray array = {};
          ErrorCode err = path->to_polygons(false, Tag{0, 0}, array);
          if (err != ErrorCode::NoError)
            error_code = err;
          for (auto& poly : array) {
            len = poly->points.size();
            if (len > polygon_max)
              polygon_max = len;
          }
        }
      }

      for (auto [e, ref] : cell->reg.view<Reference::s_ptr>().each()) {
        len = max_string_length(ref->properties);
        if (len > string_max)
          string_max = len;
      }

      for (auto [e, label] : cell->reg.view<Label::s_ptr>().each()) {
        len = label->text.length();
        if (len > string_max)
          string_max = len;
        len = max_string_length(label->properties);
        if (len > string_max)
          string_max = len;
      }
      for (auto [e, text] : cell->reg.view<Text::s_ptr>().each()) {
        len = text->text.length();
        if (len > string_max)
          string_max = len;
        len = max_string_length(text->properties);
        if (len > string_max)
          string_max = len;
      }
      for (auto [e, pin] : cell->reg.view<Pin::s_ptr>().each()) {
        len = pin->text.length();
        if (len > string_max)
          string_max = len;
        len = max_string_length(pin->properties);
        if (len > string_max)
          string_max = len;
      }
      for (auto [e, kpoint] : cell->reg.view<KeyPoint::s_ptr>().each()) {
        len = kpoint->text.length();
        if (len > string_max)
          string_max = len;
        len = max_string_length(kpoint->properties);
        if (len > string_max)
          string_max = len;
      }
    }
    tmp_array.clear();

    remove_property(properties, s_max_int_size_property_name, true);
    set_property(properties, s_max_int_size_property_name, (uint64_t)sizeof(int64_t), true);

    remove_property(properties, s_max_uint_size_property_name, true);
    set_property(properties, s_max_uint_size_property_name, (uint64_t)sizeof(uint64_t), true);

    remove_property(properties, s_max_string_size_property_name, true);
    set_property(properties, s_max_string_size_property_name, string_max, true);

    remove_property(properties, s_max_polygon_property_name, true);
    set_property(properties, s_max_polygon_property_name, polygon_max, true);

    remove_property(properties, s_max_path_property_name, true);
    set_property(properties, s_max_path_property_name, path_max, true);
  }

  ErrorCode err = DB::properties_to_oas(properties, out, state);
  if (err != ErrorCode::NoError)
    error_code = err;

  std::unordered_map<std::string, uint64_t> cell_name_map = {};
  std::unordered_map<std::string, uint64_t> cell_offset_map = {};
  std::unordered_map<std::string, uint64_t> text_string_map = {};
  bool write_cell_offsets = state.config_flags & OASIS_CONFIG_PROPERTY_CELL_OFFSET;

  // Build cell name map. Other maps are built as the file is written.
  // cell_name_map.resize((uint64_t)(2.0 + 10.0 / GDSTK_MAP_CAPACITY_THRESHOLD * c_size));
  // if (write_cell_offsets) {
  //   cell_offset_map.resize((uint64_t)(2.0 + 10.0 / GDSTK_MAP_CAPACITY_THRESHOLD * c_size));
  // }

  uint64_t cell_ref_number = 0;
  for (auto& [name, cell] : cell_map) {
    cell_name_map[name] = cell_ref_number;
    cell_ref_number++;
  }

  for (auto& [name, cell] : cell_map) {
    if (write_cell_offsets) {
      cell_offset_map[cell->name] = ftell(out.file);
    }
    oasis_putc((int)OasisRecord::CELL_REF_NUM, out);
    oasis_write_unsigned_integer(out, cell_name_map[cell->name]);

    // assert(cell_name_map[cell->name] == i);
    if (compression_level > 0) {
      out.cursor = out.data;
    }

    SPDLOG_INFO("[OAS W] ---------->cell [{}]:", cell->name);

    // TODO: Use modal variables
    // Cell contents
    for (auto [e, poly] : cell->reg.view<Polygon::s_ptr>().each()) {
      err = DB::polygon_to_oas(poly, out, state);
      if (err != ErrorCode::NoError)
        error_code = err;
    }
    SPDLOG_INFO("[OAS W] poly ok ~:");

    for (auto [e, rect] : cell->reg.view<Rectangle::s_ptr>().each()) {
      SPDLOG_INFO("[OAS W] rect: o [{}, {}], w: {} h: {} ,s: {}~:", rect->pos.x, rect->pos.y, rect->width, rect->height, state.scaling);
      err = DB::rectangle_to_oas(rect, out, state);
      if (err != ErrorCode::NoError)
        error_code = err;
    }
    SPDLOG_INFO("[OAS W] rect ok ~:");

    for (auto [e, circle] : cell->reg.view<Circle::s_ptr>().each()) {
      err = DB::circle_to_oas(circle, out, state);
      if (err != ErrorCode::NoError)
        error_code = err;
    }
    SPDLOG_INFO("[OAS W] circle ok ~:");

    for (auto [e, ellipse] : cell->reg.view<Ellipse::s_ptr>().each()) {
      err = DB::ellipse_to_oas(ellipse, out, state);
      if (err != ErrorCode::NoError)
        error_code = err;
    }
    SPDLOG_INFO("[OAS W] ellipse ok ~:");

    for (auto [e, fan] : cell->reg.view<Fan::s_ptr>().each()) {
      err = DB::fan_to_oas(fan, out, state);
      if (err != ErrorCode::NoError)
        error_code = err;
    }
    SPDLOG_INFO("[OAS W] fan ok ~:");

    for (auto [e, ruler] : cell->reg.view<Ruler::s_ptr>().each()) {
      err = DB::ruler_to_oas(ruler, out, state);
      if (err != ErrorCode::NoError)
        error_code = err;
    }
    SPDLOG_INFO("[OAS W] ruler ok ~:");

    // for (auto [e, text] : cell->reg.view<Text::s_ptr>().each()) {
    //   err = DB::text_to_oas(text, out, state);
    //   if (err != ErrorCode::NoError)
    //     error_code = err;
    // }
    // SPDLOG_INFO("[OAS W] text ok ~:");
    for (auto [e, text] : cell->reg.view<Text::s_ptr>().each()) {
      err = DB::text_to_oas(text, text_string_map, out, state);
      if (err != ErrorCode::NoError)
        error_code = err;
    }
    SPDLOG_INFO("[OAS W] label ok ~:");

    for (auto [e, pin] : cell->reg.view<Pin::s_ptr>().each()) {
      err = DB::pin_to_oas(pin, text_string_map, out, state);
      if (err != ErrorCode::NoError)
        error_code = err;
    }
    SPDLOG_INFO("[OAS W] pin ok ~:");

    for (auto [e, kpoint] : cell->reg.view<KeyPoint::s_ptr>().each()) {
      err = DB::keypoint_to_oas(kpoint, text_string_map, out, state);
      if (err != ErrorCode::NoError)
        error_code = err;
    }
    SPDLOG_INFO("[OAS W] pin ok ~:");

    for (auto [e, label] : cell->reg.view<Label::s_ptr>().each()) {
      err = DB::label_to_oas(label, text_string_map, out, state);
      if (err != ErrorCode::NoError)
        error_code = err;
    }
    SPDLOG_INFO("[OAS W] label ok ~:");

    // path
    for (auto [e, path] : cell->reg.view<Path::s_ptr>().each()) {
      err = DB::path_to_oas(path, out, state);
      if (err != ErrorCode::NoError)
        error_code = err;
    }
    SPDLOG_INFO("[OAS W] path ok ~:");

    auto ref_view = cell->reg.view<Reference::s_ptr>();

    uint32_t n = 0;
    uint32_t size = ref_view.size();
    for (auto [e, ref] : ref_view.each()) {
      // n++;
      // if (n % 1000 == 0) {
      //   SPDLOG_INFO("[OAS W] ref {:.2f}%~:", n / size);
      // }
      err = DB::reference_to_oas(ref, cell_name_map, out, state);
      if (err != ErrorCode::NoError)
        error_code = err;
    }
    SPDLOG_INFO("[OAS W] ref ok ~:");

    if (compression_level > 0) {
      uint64_t uncompressed_size = out.cursor - out.data;
      out.cursor = NULL;

      // Skip empty cells
      if (uncompressed_size > 0) {
        z_stream s = {};
        s.zalloc = zalloc;
        s.zfree = zfree;
        if (deflateInit2(&s, compression_level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
          if (error_logger)
            fputs("[GDSTK] Unable to initialize zlib.\n", error_logger);
          error_code = ErrorCode::ZlibError;
        }
        s.avail_out = deflateBound(&s, (uLong)uncompressed_size);
        uint8_t* buffer = (uint8_t*)allocate(s.avail_out);
        s.next_out = buffer;
        s.avail_in = (uInt)uncompressed_size;
        s.next_in = out.data;
        int ret = deflate(&s, Z_FINISH);
        if (ret != Z_STREAM_END) {
          if (error_logger)
            fputs("[GDSTK] Unable to compress CBLOCK.\n", error_logger);
          error_code = ErrorCode::ZlibError;
        }

        oasis_putc((int)OasisRecord::CBLOCK, out);
        oasis_putc(0, out);
        oasis_write_unsigned_integer(out, uncompressed_size);
        oasis_write_unsigned_integer(out, s.total_out);
        oasis_write(buffer, 1, s.total_out, out);
        free_allocation(buffer);
        deflateEnd(&s);
      }
    }
  }

  uint64_t cell_name_offset = c_size > 0 ? ftell(out.file) : 0;
  Map<GeometryInfo> cache = {};
  for (auto& [name, cell] : cell_map) {
    oasis_putc((int)OasisRecord::CELLNAME_IMPLICIT, out);
    // auto name_ = cell->name;
    uint64_t len = name.length();
    oasis_write_unsigned_integer(out, len);
    oasis_write(name.c_str(), 1, len, out);

    if (state.config_flags & OASIS_CONFIG_PROPERTY_BOUNDING_BOX) {
      Vec2d bbmin, bbmax;
      GeometryInfo info = cell->bounding_box(cache);
      if (info.bounding_box_min.x > info.bounding_box_max.x) {
        bbmin = Vec2d{0, 0};
        bbmax = Vec2d{0, 0};
      } else {
        bbmin = info.bounding_box_min;
        bbmax = info.bounding_box_max;
      }
      int64_t xmin = llround(bbmin.x * state.scaling);
      int64_t ymin = llround(bbmin.y * state.scaling);
      uint64_t width = llround(bbmax.x * state.scaling) - xmin;
      uint64_t height = llround(bbmax.y * state.scaling) - ymin;
      remove_property(cell->properties, s_bounding_box_property_name, true);
      set_property(cell->properties, s_bounding_box_property_name, height, true);
      set_property(cell->properties, s_bounding_box_property_name, width, false);
      set_property(cell->properties, s_bounding_box_property_name, ymin, false);
      set_property(cell->properties, s_bounding_box_property_name, xmin, false);
      set_property(cell->properties, s_bounding_box_property_name, (uint64_t)0, false);
    }
    if (write_cell_offsets) {
      remove_property(cell->properties, s_cell_offset_property_name, true);
      set_property(cell->properties, s_cell_offset_property_name, cell_offset_map[cell->name], true);
    }
    err = DB::properties_to_oas(cell->properties, out, state);
    if (err != ErrorCode::NoError)
      error_code = err;
  }
  // for (MapItem<GeometryInfo>* item = cache.next(NULL); item; item = cache.next(item)) {
  //   item->value.clear();
  // }
  // cache.clear();
  auto get_sort_map = [](std::unordered_map<std::string, uint64_t> in) {
    std::map<uint64_t, std::string> res = {};
    for (const auto& [name, num] : in) {
      res[num] = name;
    }
    return res;
  };

  uint64_t text_string_offset = text_string_map.size() > 0 ? ftell(out.file) : 0;
  auto sort_text_string_map = get_sort_map(text_string_map);
  for (const auto& [num, name] : sort_text_string_map) {
    uint64_t len = name.length();
    oasis_putc((int)OasisRecord::TEXTSTRING, out);
    oasis_write_unsigned_integer(out, len);
    oasis_write(name.c_str(), 1, len, out);
    oasis_write_unsigned_integer(out, num);
  }

  uint64_t prop_name_offset = state.property_name_map.size() > 0 ? ftell(out.file) : 0;
  auto sort_property_name_map = get_sort_map(state.property_name_map);
  for (const auto& [num, name] : sort_property_name_map) {
    uint64_t len = name.length();
    oasis_putc((int)OasisRecord::PROPNAME, out);
    oasis_write_unsigned_integer(out, len);
    oasis_write(name.c_str(), 1, len, out);
    oasis_write_unsigned_integer(out, num);
  }

  uint64_t prop_string_offset = state.property_value_name_map.size() > 0 ? ftell(out.file) : 0;
  auto sort_property_value_name_map = get_sort_map(state.property_value_name_map);
  for (const auto& [num, name] : sort_property_value_name_map) {
    uint64_t len = name.length();
    oasis_putc((int)OasisRecord::PROPSTRING, out);
    oasis_write_unsigned_integer(out, len);
    oasis_write(name.c_str(), 1, len, out);
    oasis_write_unsigned_integer(out, num);
  }
  // uint64_t prop_string_offset = state.property_value_array.size() > 0 ? ftell(out.file) : 0;
  // auto value_p = state.property_value_array.data();
  // for (uint64_t i = state.property_value_array.size(); i > 0; i--) {
  //   auto value = *value_p++;
  //   oasis_putc((int)OasisRecord::PROPSTRING_IMPLICIT, out);
  //   oasis_write_unsigned_integer(out, value->string.length());
  //   oasis_write(value->string.c_str(), 1, value->string.length(), out);
  // }

  oasis_putc((int)OasisRecord::END, out);

  // END (1) + table-offsets (?) + b-string length (2) + padding + validation (1 or 5) = 256
  uint64_t pad_len = 256 - 1 - 2 - 1 + ftell(out.file);
  if (out.crc32 || out.checksum32)
    pad_len -= 4;

  // Table offsets
  oasis_putc(1, out);
  oasis_write_unsigned_integer(out, cell_name_offset);
  oasis_putc(1, out);
  oasis_write_unsigned_integer(out, text_string_offset);
  oasis_putc(1, out);
  oasis_write_unsigned_integer(out, prop_name_offset);
  oasis_putc(1, out);
  oasis_write_unsigned_integer(out, prop_string_offset);
  oasis_putc(1, out);
  oasis_putc(0, out);  // LAYERNAME table
  oasis_putc(1, out);
  oasis_putc(0, out);  // XNAME table

  pad_len -= ftell(out.file);
  oasis_write_unsigned_integer(out, pad_len);
  for (; pad_len > 0; pad_len--)
    oasis_putc(0, out);

  if (out.crc32) {
    oasis_putc(1, out);
    little_endian_swap32(&out.signature, 1);
    fwrite(&out.signature, 4, 1, out.file);
  } else if (out.checksum32) {
    oasis_putc(2, out);
    little_endian_swap32(&out.signature, 1);
    fwrite(&out.signature, 4, 1, out.file);
  } else {
    oasis_putc(0, out);
  }

  fclose(out.file);
  free_allocation(out.data);

  return error_code;
}

// write svg

// ErrorCode DB::write_svg(const Polygon::s_ptr poly, FILE* out, double scaling, uint32_t precision) {
//   if (poly->points.size() < 3)
//     return ErrorCode::NoError;

//   char double_buffer[GDSTK_DOUBLE_BUFFER_COUNT];
//   fprintf(out, "<polygon id=\"%p\" class=\"l%" PRIu32 "d%" PRIu32 "\" points=\"", poly.get(), poly->layer, poly->datatype);
//   Vec2d* p = poly->points.data();
//   for (uint64_t j = 0; j < poly->points.size() - 1; j++) {
//     fputs(double_print(p->x * scaling, precision, double_buffer, COUNT(double_buffer)), out);
//     fputc(',', out);
//     fputs(double_print(p->y * scaling, precision, double_buffer, COUNT(double_buffer)), out);
//     fputc(' ', out);
//     p++;
//   }
//   fputs(double_print(p->x * scaling, precision, double_buffer, COUNT(double_buffer)), out);
//   fputc(',', out);
//   fputs(double_print(p->y * scaling, precision, double_buffer, COUNT(double_buffer)), out);
//   fputs("\"/>\n", out);
//   if (poly->repetition.type != RepetitionType::None) {
//     Array<Vec2d> offsets = {};
//     poly->repetition.get_offsets(offsets);
//     double* offset_p = (double*)(offsets.data() + 1);
//     for (uint64_t offset_count = offsets.size() - 1; offset_count > 0; offset_count--) {
//       double offset_x = *offset_p++;
//       double offset_y = *offset_p++;
//       fprintf(out, "<use href=\"#%p\" x=\"", poly.get());
//       fputs(double_print(offset_x * scaling, precision, double_buffer, COUNT(double_buffer)), out);
//       fputs("\" y=\"", out);
//       fputs(double_print(offset_y * scaling, precision, double_buffer, COUNT(double_buffer)), out);
//       fputs("\"/>\n", out);
//     }
//     offsets.clear();
//   }
//   return ErrorCode::NoError;
// }

// ErrorCode DB::write_svg(const Label::s_ptr label, FILE* out, double scaling, uint32_t precision) {
//   fprintf(out, "<text id=\"%p\" class=\"l%" PRIu32 "t%" PRIu32 "\"", label.get(), label->layer, label->datatype);
//   switch (label->anchor) {
//     case Anchor::NW:
//     case Anchor::W:
//     case Anchor::SW:
//       fputs(" text-anchor=\"start\"", out);
//       break;
//     case Anchor::N:
//     case Anchor::O:
//     case Anchor::S:
//       fputs(" text-anchor=\"middle\"", out);
//       break;
//     case Anchor::NE:
//     case Anchor::E:
//     case Anchor::SE:
//       fputs(" text-anchor=\"end\"", out);
//       break;
//   }
//   switch (label->anchor) {
//     case Anchor::NW:
//     case Anchor::N:
//     case Anchor::NE:
//       fputs(" dominant-baseline=\"text-before-edge\"", out);
//       break;
//     case Anchor::W:
//     case Anchor::O:
//     case Anchor::E:
//       fputs(" dominant-baseline=\"central\"", out);
//       break;
//     case Anchor::SW:
//     case Anchor::S:
//     case Anchor::SE:
//       fputs(" dominant-baseline=\"text-after-edge\"", out);
//       break;
//   }

//   char double_buffer[GDSTK_DOUBLE_BUFFER_COUNT];
//   fputs(" transform=\"translate(", out);
//   fputs(double_print(scaling * label->origin.x, precision, double_buffer, COUNT(double_buffer)), out);
//   fputc(' ', out);
//   fputs(double_print(scaling * label->origin.y, precision, double_buffer, COUNT(double_buffer)), out);
//   fputc(')', out);

//   if (label->rotation != 0) {
//     fputs(" rotate(", out);
//     fputs(double_print(label->rotation * (180.0 / M_PI), precision, double_buffer, COUNT(double_buffer)), out);
//     fputc(')', out);
//   }
//   if (label->x_reflection) {
//     fputs(" scale(1 -1)", out);
//   }
//   if (label->magnification != 1) {
//     fputs(" scale(", out);
//     fputs(double_print(label->magnification, precision, double_buffer, COUNT(double_buffer)), out);
//     fputc(')', out);
//   }

//   // NOTE: Escape “<”, “>”, and “&” inside the SVG tag.  Here be dragons if the text is not ASCII.
//   // The GDSII specification imposes ASCII-only for strings, but who knows…
//   fputs(" scale(1 -1)\">", out);
//   int index_ = 0;
//   for (const char* c = label->text.c_str(); index_ < label->text.size(); index_++, c++) {
//     switch (*c) {
//       case '<':
//         fputs("&lt;", out);
//         break;
//       case '>':
//         fputs("&gt;", out);
//         break;
//       case '&':
//         fputs("&amp;", out);
//         break;
//       default:
//         putc(*c, out);
//     }
//   }
//   fputs("</text>\n", out);

//   if (label->repetition.type != RepetitionType::None) {
//     Array<Vec2d> offsets = {};
//     label->repetition.get_offsets(offsets);
//     double* offset_p = (double*)(offsets.data() + 1);
//     for (uint64_t offset_count = offsets.size() - 1; offset_count > 0; offset_count--) {
//       double offset_x = *offset_p++;
//       double offset_y = *offset_p++;
//       fprintf(out, "<use href=\"#%p\" x=\"", label.get());
//       fputs(double_print(offset_x * scaling, precision, double_buffer, COUNT(double_buffer)), out);
//       fputs("\" y=\"", out);
//       fputs(double_print(offset_y * scaling, precision, double_buffer, COUNT(double_buffer)), out);
//       fputs("\"/>\n", out);
//     }
//     offsets.clear();
//   }
//   return ErrorCode::NoError;
// }

// ErrorCode DB::write_svg(const Path::s_ptr path, FILE* out, double scaling, uint32_t precision) {
//   PolygonPtrArray array = {};
//   ErrorCode error_code = path->to_polygons(false, 0, 0, array);
//   for (uint64_t i = 0; i < array.size(); i++) {
//     ErrorCode err = DB::write_svg(array[i], out, scaling, precision);
//     if (err != ErrorCode::NoError)
//       error_code = err;
//   }
//   array.clear();
//   return error_code;
// }

// ErrorCode DB::write_svg(const Reference::s_ptr reference, FILE* out, double scaling, uint32_t precision) {
//   const char* src_name = reference->type == ReferenceType::Cell ? reference->cell->name.c_str() : reference->name.c_str();
//   char* ref_name = (char*)allocate(strlen(src_name) + 1);

//   char* d = ref_name;
//   int index_ = 0;
//   for (const char* c = src_name; index_ < strlen(src_name); c++, d++, index_++)
//     *d = *c == '#' ? '_' : *c;
//   *d = 0;

//   Vec2d zero = {0, 0};
//   Array<Vec2d> offsets = {};
//   if (reference->repetition.type != RepetitionType::None) {
//     reference->repetition.get_offsets(offsets);
//   } else {
//     offsets.push_back(zero);
//   }

//   char double_buffer[GDSTK_DOUBLE_BUFFER_COUNT];
//   double* offset_p = (double*)offsets.data();
//   for (uint64_t offset_count = offsets.size(); offset_count > 0; offset_count--) {
//     double offset_x = scaling * (reference->origin.x + *offset_p++);
//     double offset_y = scaling * (reference->origin.y + *offset_p++);
//     fputs("<use transform=\"translate(", out);
//     fputs(double_print(offset_x, precision, double_buffer, COUNT(double_buffer)), out);
//     fputc(' ', out);
//     fputs(double_print(offset_y, precision, double_buffer, COUNT(double_buffer)), out);
//     fputc(')', out);
//     if (reference->rotation != 0) {
//       fputs(" rotate(", out);
//       fputs(double_print(reference->rotation * (180.0 / M_PI), precision, double_buffer, COUNT(double_buffer)), out);
//       fputc(')', out);
//     }
//     if (reference->x_reflection) {
//       fputs(" scale(1 -1)", out);
//     }
//     if (reference->magnification != 1) {
//       fputs(" scale(", out);
//       fputs(double_print(reference->magnification, precision, double_buffer, COUNT(double_buffer)), out);
//       fputc(')', out);
//     }
//     fprintf(out, "\" xlink:href=\"#%s\"/>\n", ref_name);
//   }
//   free_allocation(ref_name);
//   if (reference->repetition.type != RepetitionType::None)
//     offsets.clear();
//   return ErrorCode::NoError;
// }

// ErrorCode DB::write_svg(const Cell::s_ptr cell, FILE* out, double scaling, uint32_t precision, std::string attributes) {
//   ErrorCode error_code = ErrorCode::NoError;
//   char* buffer = (char*)allocate(cell->name.size() + 1);

//   char* d = buffer;
//   int index_ = 0;
//   for (char* c = cell->name.data(); index_ < cell->name.size(); c++, d++, index_++)
//     *d = *c == '#' ? '_' : *c;
//   *d = 0;

//   std::string attributes_t = attributes + '0';
//   if (attributes.empty()) {
//     fprintf(out, "<g id=\"%s\" %s>\n", buffer, attributes_t.data());
//   } else {
//     fprintf(out, "<g id=\"%s\">\n", buffer);
//   }

//   Polygon::s_ptr* polygon = cell->polygons.data();
//   for (uint64_t i = 0; i < cell->polygons.size(); i++, polygon++) {
//     ErrorCode err = write_svg(*polygon, out, scaling, precision);
//     if (err != ErrorCode::NoError)
//       error_code = err;
//   }

//   Path::s_ptr* flexpath = cell->paths.data();
//   for (uint64_t i = 0; i < cell->paths.size(); i++, flexpath++) {
//     ErrorCode err = write_svg(*flexpath, out, scaling, precision);
//     if (err != ErrorCode::NoError)
//       error_code = err;
//   }

//   Reference::s_ptr* reference = cell->references.data();
//   for (uint64_t i = 0; i < cell->references.size(); i++, reference++) {
//     ErrorCode err = write_svg(*reference, out, scaling, precision);
//     if (err != ErrorCode::NoError)
//       error_code = err;
//   }

//   Label::s_ptr* label = cell->labels.data();
//   for (uint64_t i = 0; i < cell->labels.size(); i++, label++) {
//     ErrorCode err = write_svg(*label, out, scaling, precision);
//     if (err != ErrorCode::NoError)
//       error_code = err;
//   }

//   fputs("</g>\n", out);
//   free_allocation(buffer);
//   return error_code;
// }

// ErrorCode DB::write_svg(const Cell::s_ptr cell, std::string filename, double scaling, uint32_t precision, std::string background, double pad,
//                         bool pad_as_percentage) {
//   ErrorCode error_code = ErrorCode::NoError;
//   Vec2dArray box = cell->bounding_box();
//   Vec2d min = box[0], max = box[1];
//   if (min.x > max.x) {
//     min = Vec2d{0, 0};
//     max = Vec2d{1, 1};
//   }

//   min *= scaling;
//   max *= scaling;
//   double x = min.x;
//   double y = -max.y;
//   double w = max.x - min.x;
//   double h = max.y - min.y;

//   if (pad_as_percentage)
//     pad *= (w > h ? w : h) / 100;
//   x -= pad;
//   y -= pad;
//   w += 2 * pad;
//   h += 2 * pad;

//   FILE* out = fopen(filename.c_str(), "w");
//   if (out == NULL) {
//     if (error_logger)
//       fputs("[GDSTK] Unable to open file for SVG output.\n", error_logger);
//     return ErrorCode::OutputFileOpenError;
//   }

//   fputs(
//       "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
//       "<svg xmlns=\"http://www.w3.org/2000/svg\" "
//       "xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"",
//       out);
//   char double_buffer[GDSTK_DOUBLE_BUFFER_COUNT];
//   fputs(double_print(w, precision, double_buffer, COUNT(double_buffer)), out);
//   fputs("\" height=\"", out);
//   fputs(double_print(h, precision, double_buffer, COUNT(double_buffer)), out);
//   fputs("\" viewBox=\"", out);
//   fputs(double_print(x, precision, double_buffer, COUNT(double_buffer)), out);
//   fputc(' ', out);
//   fputs(double_print(y, precision, double_buffer, COUNT(double_buffer)), out);
//   fputc(' ', out);
//   fputs(double_print(w, precision, double_buffer, COUNT(double_buffer)), out);
//   fputc(' ', out);
//   fputs(double_print(h, precision, double_buffer, COUNT(double_buffer)), out);
//   fputs("\">\n<defs>\n<style type=\"text/css\">\n", out);

//   Map<Cell::s_ptr> cell_map = {};
//   cell->dependencies(true, cell_map);

//   std::set<Tag> shape_tags = {};
//   // get_shape_tags(shape_tags);

//   // Set<Tag> label_tags = {};
//   // get_label_tags(label_tags);

//   // for (MapItem<Cell*>* item = cell_map.next(NULL); item != NULL; item = cell_map.next(item)) {
//   //   item->value->get_shape_tags(shape_tags);
//   //   item->value->get_label_tags(label_tags);
//   // }

//   // if (shape_style) {
//   //   for (SetItem<Tag>* item = shape_tags.next(NULL); item; item = shape_tags.next(item)) {
//   //     Tag tag = item->value;
//   //     const char* style = shape_style->get(tag);
//   //     if (!style)
//   //       style = default_svg_shape_style(tag);
//   //     fprintf(out, ".l%" PRIu32 "d%" PRIu32 " {%s}\n", get_layer(tag), get_type(tag), style);
//   //   }
//   // } else {
//   //   for (SetItem<Tag>* item = shape_tags.next(NULL); item; item = shape_tags.next(item)) {
//   //     Tag tag = item->value;
//   //     const char* style = default_svg_shape_style(tag);
//   //     fprintf(out, ".l%" PRIu32 "d%" PRIu32 " {%s}\n", get_layer(tag), get_type(tag), style);
//   //   }
//   // }

//   // if (label_style) {
//   //   for (SetItem<Tag>* item = label_tags.next(NULL); item; item = label_tags.next(item)) {
//   //     Tag tag = item->value;
//   //     const char* style = label_style->get(tag);
//   //     if (!style)
//   //       style = default_svg_label_style(tag);
//   //     fprintf(out, ".l%" PRIu32 "t%" PRIu32 " {%s}\n", get_layer(tag), get_type(tag), style);
//   //   }
//   // } else {
//   //   for (SetItem<Tag>* item = label_tags.next(NULL); item; item = label_tags.next(item)) {
//   //     Tag tag = item->value;
//   //     const char* style = default_svg_label_style(tag);
//   //     fprintf(out, ".l%" PRIu32 "t%" PRIu32 " {%s}\n", get_layer(tag), get_type(tag), style);
//   //   }
//   // }

//   // fputs("</style>\n", out);

//   // for (MapItem<Cell*>* item = cell_map.next(NULL); item != NULL; item = cell_map.next(item)) {
//   //   ErrorCode err = item->value->to_svg(out, scaling, precision, NULL, comparison);
//   //   if (err != ErrorCode::NoError)
//   //     error_code = err;
//   // }

//   // cell_map.clear();
//   // shape_tags.clear();
//   // label_tags.clear();

//   // fputs("</defs>\n", out);
//   // if (!background.empty()) {
//   //   fputs("<rect x=\"", out);
//   //   fputs(double_print(x, precision, double_buffer, COUNT(double_buffer)), out);
//   //   fputs("\" y=\"", out);
//   //   fputs(double_print(y, precision, double_buffer, COUNT(double_buffer)), out);
//   //   fputs("\" width=\"", out);
//   //   fputs(double_print(w, precision, double_buffer, COUNT(double_buffer)), out);
//   //   fputs("\" height=\"", out);
//   //   fputs(double_print(h, precision, double_buffer, COUNT(double_buffer)), out);
//   //   fprintf(out, "\" fill=\"%s\" stroke=\"none\"/>\n", background);
//   // }
//   // ErrorCode err = write_svg(cell, out, scaling, precision, "transform=\"scale(1 -1)\"");
//   // if (err != ErrorCode::NoError)
//   //   error_code = err;
//   // fputs("</svg>", out);
//   // fclose(out);
//   return error_code;
// }

}  // namespace qlib